Bug 1890689 apply drift correction to input rate instead of output rate r=pehrsons
[gecko.git] / editor / libeditor / HTMLEditSubActionHandler.cpp
blob28e0ad159582f01aa880af5435fec3941fabdda2
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/. */
7 #include "HTMLEditor.h"
8 #include "HTMLEditorInlines.h"
9 #include "HTMLEditorNestedClasses.h"
11 #include <algorithm>
12 #include <utility>
14 #include "AutoRangeArray.h"
15 #include "CSSEditUtils.h"
16 #include "EditAction.h"
17 #include "EditorDOMPoint.h"
18 #include "EditorUtils.h"
19 #include "HTMLEditHelpers.h"
20 #include "HTMLEditUtils.h"
21 #include "PendingStyles.h" // for SpecifiedStyle
22 #include "WSRunObject.h"
24 #include "ErrorList.h"
25 #include "mozilla/Assertions.h"
26 #include "mozilla/Attributes.h"
27 #include "mozilla/AutoRestore.h"
28 #include "mozilla/CheckedInt.h"
29 #include "mozilla/ContentIterator.h"
30 #include "mozilla/EditorForwards.h"
31 #include "mozilla/IntegerRange.h"
32 #include "mozilla/InternalMutationEvent.h"
33 #include "mozilla/MathAlgorithms.h"
34 #include "mozilla/Maybe.h"
35 #include "mozilla/OwningNonNull.h"
36 #include "mozilla/Preferences.h"
37 #include "mozilla/PresShell.h"
38 #include "mozilla/RangeUtils.h"
39 #include "mozilla/StaticPrefs_editor.h" // for StaticPrefs::editor_*
40 #include "mozilla/TextComposition.h"
41 #include "mozilla/UniquePtr.h"
42 #include "mozilla/Unused.h"
43 #include "mozilla/dom/AncestorIterator.h"
44 #include "mozilla/dom/Element.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 "mozilla/mozalloc.h"
50 #include "nsAString.h"
51 #include "nsAlgorithm.h"
52 #include "nsAtom.h"
53 #include "nsCRT.h"
54 #include "nsCRTGlue.h"
55 #include "nsComponentManagerUtils.h"
56 #include "nsContentUtils.h"
57 #include "nsDebug.h"
58 #include "nsError.h"
59 #include "nsFrameSelection.h"
60 #include "nsGkAtoms.h"
61 #include "nsHTMLDocument.h"
62 #include "nsIContent.h"
63 #include "nsID.h"
64 #include "nsIFrame.h"
65 #include "nsINode.h"
66 #include "nsLiteralString.h"
67 #include "nsPrintfCString.h"
68 #include "nsRange.h"
69 #include "nsReadableUtils.h"
70 #include "nsString.h"
71 #include "nsStringFwd.h"
72 #include "nsStyledElement.h"
73 #include "nsTArray.h"
74 #include "nsTextNode.h"
75 #include "nsThreadUtils.h"
76 #include "nsUnicharUtils.h"
78 class nsISupports;
80 namespace mozilla {
82 using namespace dom;
83 using EmptyCheckOption = HTMLEditUtils::EmptyCheckOption;
84 using EmptyCheckOptions = HTMLEditUtils::EmptyCheckOptions;
85 using LeafNodeType = HTMLEditUtils::LeafNodeType;
86 using LeafNodeTypes = HTMLEditUtils::LeafNodeTypes;
87 using WalkTextOption = HTMLEditUtils::WalkTextOption;
88 using WalkTreeDirection = HTMLEditUtils::WalkTreeDirection;
89 using WalkTreeOption = HTMLEditUtils::WalkTreeOption;
91 /********************************************************
92 * first some helpful functors we will use
93 ********************************************************/
95 static bool IsPendingStyleCachePreservingSubAction(
96 EditSubAction aEditSubAction) {
97 switch (aEditSubAction) {
98 case EditSubAction::eDeleteSelectedContent:
99 case EditSubAction::eInsertLineBreak:
100 case EditSubAction::eInsertParagraphSeparator:
101 case EditSubAction::eCreateOrChangeList:
102 case EditSubAction::eIndent:
103 case EditSubAction::eOutdent:
104 case EditSubAction::eSetOrClearAlignment:
105 case EditSubAction::eCreateOrRemoveBlock:
106 case EditSubAction::eFormatBlockForHTMLCommand:
107 case EditSubAction::eMergeBlockContents:
108 case EditSubAction::eRemoveList:
109 case EditSubAction::eCreateOrChangeDefinitionListItem:
110 case EditSubAction::eInsertElement:
111 case EditSubAction::eInsertQuotation:
112 case EditSubAction::eInsertQuotedText:
113 return true;
114 default:
115 return false;
119 template already_AddRefed<nsRange>
120 HTMLEditor::CreateRangeIncludingAdjuscentWhiteSpaces(
121 const EditorDOMRange& aRange);
122 template already_AddRefed<nsRange>
123 HTMLEditor::CreateRangeIncludingAdjuscentWhiteSpaces(
124 const EditorRawDOMRange& aRange);
125 template already_AddRefed<nsRange>
126 HTMLEditor::CreateRangeIncludingAdjuscentWhiteSpaces(
127 const EditorDOMPoint& aStartPoint, const EditorDOMPoint& aEndPoint);
128 template already_AddRefed<nsRange>
129 HTMLEditor::CreateRangeIncludingAdjuscentWhiteSpaces(
130 const EditorRawDOMPoint& aStartPoint, const EditorDOMPoint& aEndPoint);
131 template already_AddRefed<nsRange>
132 HTMLEditor::CreateRangeIncludingAdjuscentWhiteSpaces(
133 const EditorDOMPoint& aStartPoint, const EditorRawDOMPoint& aEndPoint);
134 template already_AddRefed<nsRange>
135 HTMLEditor::CreateRangeIncludingAdjuscentWhiteSpaces(
136 const EditorRawDOMPoint& aStartPoint, const EditorRawDOMPoint& aEndPoint);
138 nsresult HTMLEditor::InitEditorContentAndSelection() {
139 MOZ_ASSERT(IsEditActionDataAvailable());
141 // We should do nothing with the result of GetRoot() if only a part of the
142 // document is editable.
143 if (!EntireDocumentIsEditable()) {
144 return NS_OK;
147 nsresult rv = MaybeCreatePaddingBRElementForEmptyEditor();
148 if (NS_FAILED(rv)) {
149 NS_WARNING(
150 "HTMLEditor::MaybeCreatePaddingBRElementForEmptyEditor() failed");
151 return rv;
154 // If the selection hasn't been set up yet, set it up collapsed to the end of
155 // our editable content.
156 // XXX I think that this shouldn't do it in `HTMLEditor` because it maybe
157 // removed by the web app and if they call `Selection::AddRange()` without
158 // checking the range count, it may cause multiple selection ranges.
159 if (!SelectionRef().RangeCount()) {
160 nsresult rv = CollapseSelectionToEndOfLastLeafNodeOfDocument();
161 if (NS_FAILED(rv)) {
162 NS_WARNING(
163 "HTMLEditor::CollapseSelectionToEndOfLastLeafNodeOfDocument() "
164 "failed");
165 return rv;
169 if (IsPlaintextMailComposer()) {
170 // XXX Should we do this in HTMLEditor? It's odd to guarantee that last
171 // empty line is visible only when it's in the plain text mode.
172 nsresult rv = EnsurePaddingBRElementInMultilineEditor();
173 if (NS_FAILED(rv)) {
174 NS_WARNING(
175 "EditorBase::EnsurePaddingBRElementInMultilineEditor() failed");
176 return rv;
180 Element* bodyOrDocumentElement = GetRoot();
181 if (NS_WARN_IF(!bodyOrDocumentElement && !GetDocument())) {
182 return NS_ERROR_FAILURE;
185 if (!bodyOrDocumentElement) {
186 return NS_OK;
189 rv = InsertBRElementToEmptyListItemsAndTableCellsInRange(
190 RawRangeBoundary(bodyOrDocumentElement, 0u),
191 RawRangeBoundary(bodyOrDocumentElement,
192 bodyOrDocumentElement->GetChildCount()));
193 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
194 return NS_ERROR_EDITOR_DESTROYED;
196 NS_WARNING_ASSERTION(
197 NS_SUCCEEDED(rv),
198 "HTMLEditor::InsertBRElementToEmptyListItemsAndTableCellsInRange() "
199 "failed, but ignored");
200 return NS_OK;
203 void HTMLEditor::OnStartToHandleTopLevelEditSubAction(
204 EditSubAction aTopLevelEditSubAction,
205 nsIEditor::EDirection aDirectionOfTopLevelEditSubAction, ErrorResult& aRv) {
206 MOZ_ASSERT(IsEditActionDataAvailable());
207 MOZ_ASSERT(!aRv.Failed());
209 EditorBase::OnStartToHandleTopLevelEditSubAction(
210 aTopLevelEditSubAction, aDirectionOfTopLevelEditSubAction, aRv);
212 MOZ_ASSERT(GetTopLevelEditSubAction() == aTopLevelEditSubAction);
213 MOZ_ASSERT(GetDirectionOfTopLevelEditSubAction() ==
214 aDirectionOfTopLevelEditSubAction);
216 if (NS_WARN_IF(Destroyed())) {
217 aRv.Throw(NS_ERROR_EDITOR_DESTROYED);
218 return;
221 if (!mInitSucceeded) {
222 return; // We should do nothing if we're being initialized.
225 NS_WARNING_ASSERTION(
226 !aRv.Failed(),
227 "EditorBase::OnStartToHandleTopLevelEditSubAction() failed");
229 // Let's work with the latest layout information after (maybe) dispatching
230 // `beforeinput` event.
231 RefPtr<Document> document = GetDocument();
232 if (NS_WARN_IF(!document)) {
233 aRv.Throw(NS_ERROR_UNEXPECTED);
234 return;
236 document->FlushPendingNotifications(FlushType::Frames);
237 if (NS_WARN_IF(Destroyed())) {
238 aRv.Throw(NS_ERROR_EDITOR_DESTROYED);
239 return;
242 // Remember where our selection was before edit action took place:
243 const auto atCompositionStart =
244 GetFirstIMESelectionStartPoint<EditorRawDOMPoint>();
245 if (atCompositionStart.IsSet()) {
246 // If there is composition string, let's remember current composition
247 // range.
248 TopLevelEditSubActionDataRef().mSelectedRange->StoreRange(
249 atCompositionStart, GetLastIMESelectionEndPoint<EditorRawDOMPoint>());
250 } else {
251 // Get the selection location
252 // XXX This may occur so that I think that we shouldn't throw exception
253 // in this case.
254 if (NS_WARN_IF(!SelectionRef().RangeCount())) {
255 aRv.Throw(NS_ERROR_UNEXPECTED);
256 return;
258 if (const nsRange* range = SelectionRef().GetRangeAt(0)) {
259 TopLevelEditSubActionDataRef().mSelectedRange->StoreRange(*range);
263 // Register with range updater to track this as we perturb the doc
264 RangeUpdaterRef().RegisterRangeItem(
265 *TopLevelEditSubActionDataRef().mSelectedRange);
267 // Remember current inline styles for deletion and normal insertion ops
268 const bool cacheInlineStyles = [&]() {
269 switch (aTopLevelEditSubAction) {
270 case EditSubAction::eInsertText:
271 case EditSubAction::eInsertTextComingFromIME:
272 case EditSubAction::eDeleteSelectedContent:
273 return true;
274 default:
275 return IsPendingStyleCachePreservingSubAction(aTopLevelEditSubAction);
277 }();
278 if (cacheInlineStyles) {
279 const RefPtr<Element> editingHost =
280 ComputeEditingHost(LimitInBodyElement::No);
281 if (NS_WARN_IF(!editingHost)) {
282 aRv.Throw(NS_ERROR_FAILURE);
283 return;
286 nsIContent* const startContainer =
287 HTMLEditUtils::GetContentToPreserveInlineStyles(
288 TopLevelEditSubActionDataRef()
289 .mSelectedRange->StartPoint<EditorRawDOMPoint>(),
290 *editingHost);
291 if (NS_WARN_IF(!startContainer)) {
292 aRv.Throw(NS_ERROR_FAILURE);
293 return;
295 if (const RefPtr<Element> startContainerElement =
296 startContainer->GetAsElementOrParentElement()) {
297 nsresult rv = CacheInlineStyles(*startContainerElement);
298 if (NS_FAILED(rv)) {
299 NS_WARNING("HTMLEditor::CacheInlineStyles() failed");
300 aRv.Throw(rv);
301 return;
306 // Stabilize the document against contenteditable count changes
307 if (document->GetEditingState() == Document::EditingState::eContentEditable) {
308 document->ChangeContentEditableCount(nullptr, +1);
309 TopLevelEditSubActionDataRef().mRestoreContentEditableCount = true;
312 // Check that selection is in subtree defined by body node
313 nsresult rv = EnsureSelectionInBodyOrDocumentElement();
314 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
315 aRv.Throw(NS_ERROR_EDITOR_DESTROYED);
316 return;
318 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
319 "HTMLEditor::EnsureSelectionInBodyOrDocumentElement() "
320 "failed, but ignored");
323 nsresult HTMLEditor::OnEndHandlingTopLevelEditSubAction() {
324 MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable());
326 nsresult rv;
327 while (true) {
328 if (NS_WARN_IF(Destroyed())) {
329 rv = NS_ERROR_EDITOR_DESTROYED;
330 break;
333 if (!mInitSucceeded) {
334 rv = NS_OK; // We should do nothing if we're being initialized.
335 break;
338 // Do all the tricky stuff
339 rv = OnEndHandlingTopLevelEditSubActionInternal();
340 NS_WARNING_ASSERTION(
341 NS_SUCCEEDED(rv),
342 "HTMLEditor::OnEndHandlingTopLevelEditSubActionInternal() failied");
343 // Perhaps, we need to do the following jobs even if the editor has been
344 // destroyed since they adjust some states of HTML document but don't
345 // modify the DOM tree nor Selection.
347 // Free up selectionState range item
348 if (TopLevelEditSubActionDataRef().mSelectedRange) {
349 RangeUpdaterRef().DropRangeItem(
350 *TopLevelEditSubActionDataRef().mSelectedRange);
353 // Reset the contenteditable count to its previous value
354 if (TopLevelEditSubActionDataRef().mRestoreContentEditableCount) {
355 Document* document = GetDocument();
356 if (NS_WARN_IF(!document)) {
357 rv = NS_ERROR_FAILURE;
358 break;
360 if (document->GetEditingState() ==
361 Document::EditingState::eContentEditable) {
362 document->ChangeContentEditableCount(nullptr, -1);
365 break;
367 DebugOnly<nsresult> rvIgnored =
368 EditorBase::OnEndHandlingTopLevelEditSubAction();
369 NS_WARNING_ASSERTION(
370 NS_FAILED(rv) || NS_SUCCEEDED(rvIgnored),
371 "EditorBase::OnEndHandlingTopLevelEditSubAction() failed, but ignored");
372 MOZ_ASSERT(!GetTopLevelEditSubAction());
373 MOZ_ASSERT(GetDirectionOfTopLevelEditSubAction() == eNone);
374 return rv;
377 nsresult HTMLEditor::OnEndHandlingTopLevelEditSubActionInternal() {
378 MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable());
380 nsresult rv = EnsureSelectionInBodyOrDocumentElement();
381 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
382 return NS_ERROR_EDITOR_DESTROYED;
384 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
385 "HTMLEditor::EnsureSelectionInBodyOrDocumentElement() "
386 "failed, but ignored");
388 switch (GetTopLevelEditSubAction()) {
389 case EditSubAction::eReplaceHeadWithHTMLSource:
390 case EditSubAction::eCreatePaddingBRElementForEmptyEditor:
391 return NS_OK;
392 default:
393 break;
396 if (TopLevelEditSubActionDataRef().mChangedRange->IsPositioned() &&
397 GetTopLevelEditSubAction() != EditSubAction::eUndo &&
398 GetTopLevelEditSubAction() != EditSubAction::eRedo) {
399 // don't let any txns in here move the selection around behind our back.
400 // Note that this won't prevent explicit selection setting from working.
401 AutoTransactionsConserveSelection dontChangeMySelection(*this);
404 EditorDOMRange changedRange(
405 *TopLevelEditSubActionDataRef().mChangedRange);
406 if (changedRange.IsPositioned() &&
407 changedRange.EnsureNotInNativeAnonymousSubtree()) {
408 bool isBlockLevelSubAction = false;
409 switch (GetTopLevelEditSubAction()) {
410 case EditSubAction::eInsertText:
411 case EditSubAction::eInsertTextComingFromIME:
412 case EditSubAction::eInsertLineBreak:
413 case EditSubAction::eInsertParagraphSeparator:
414 case EditSubAction::eDeleteText: {
415 // XXX We should investigate whether this is really needed because
416 // it seems that the following code does not handle the
417 // white-spaces.
418 RefPtr<nsRange> extendedChangedRange =
419 CreateRangeIncludingAdjuscentWhiteSpaces(changedRange);
420 if (extendedChangedRange) {
421 MOZ_ASSERT(extendedChangedRange->IsPositioned());
422 // Use extended range temporarily.
423 TopLevelEditSubActionDataRef().mChangedRange =
424 std::move(extendedChangedRange);
426 break;
428 case EditSubAction::eCreateOrChangeList:
429 case EditSubAction::eCreateOrChangeDefinitionListItem:
430 case EditSubAction::eRemoveList:
431 case EditSubAction::eFormatBlockForHTMLCommand:
432 case EditSubAction::eCreateOrRemoveBlock:
433 case EditSubAction::eIndent:
434 case EditSubAction::eOutdent:
435 case EditSubAction::eSetOrClearAlignment:
436 case EditSubAction::eSetPositionToAbsolute:
437 case EditSubAction::eSetPositionToStatic:
438 case EditSubAction::eDecreaseZIndex:
439 case EditSubAction::eIncreaseZIndex:
440 isBlockLevelSubAction = true;
441 [[fallthrough]];
442 default: {
443 Element* editingHost = ComputeEditingHost();
444 if (MOZ_UNLIKELY(!editingHost)) {
445 break;
447 RefPtr<nsRange> extendedChangedRange = AutoRangeArray::
448 CreateRangeWrappingStartAndEndLinesContainingBoundaries(
449 changedRange, GetTopLevelEditSubAction(),
450 isBlockLevelSubAction
451 ? BlockInlineCheck::UseHTMLDefaultStyle
452 : BlockInlineCheck::UseComputedDisplayOutsideStyle,
453 *editingHost);
454 if (!extendedChangedRange) {
455 break;
457 MOZ_ASSERT(extendedChangedRange->IsPositioned());
458 // Use extended range temporarily.
459 TopLevelEditSubActionDataRef().mChangedRange =
460 std::move(extendedChangedRange);
461 break;
467 // if we did a ranged deletion or handling backspace key, make sure we have
468 // a place to put caret.
469 // Note we only want to do this if the overall operation was deletion,
470 // not if deletion was done along the way for
471 // EditSubAction::eInsertHTMLSource, EditSubAction::eInsertText, etc.
472 // That's why this is here rather than DeleteSelectionAsSubAction().
473 // However, we shouldn't insert <br> elements if we've already removed
474 // empty block parents because users may want to disappear the line by
475 // the deletion.
476 // XXX We should make HandleDeleteSelection() store expected container
477 // for handling this here since we cannot trust current selection is
478 // collapsed at deleted point.
479 if (GetTopLevelEditSubAction() == EditSubAction::eDeleteSelectedContent &&
480 TopLevelEditSubActionDataRef().mDidDeleteNonCollapsedRange &&
481 !TopLevelEditSubActionDataRef().mDidDeleteEmptyParentBlocks) {
482 const auto newCaretPosition =
483 GetFirstSelectionStartPoint<EditorDOMPoint>();
484 if (!newCaretPosition.IsSet()) {
485 NS_WARNING("There was no selection range");
486 return NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE;
488 Result<CaretPoint, nsresult> caretPointOrError =
489 InsertBRElementIfHardLineIsEmptyAndEndsWithBlockBoundary(
490 newCaretPosition);
491 if (MOZ_UNLIKELY(caretPointOrError.isErr())) {
492 NS_WARNING(
493 "HTMLEditor::"
494 "InsertBRElementIfHardLineIsEmptyAndEndsWithBlockBoundary() "
495 "failed");
496 return caretPointOrError.unwrapErr();
498 nsresult rv = caretPointOrError.unwrap().SuggestCaretPointTo(
499 *this, {SuggestCaret::OnlyIfHasSuggestion});
500 if (NS_FAILED(rv)) {
501 NS_WARNING("CaretPoint::SuggestCaretPointTo() failed");
502 return rv;
504 NS_WARNING_ASSERTION(
505 rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
506 "CaretPoint::SuggestCaretPointTo() failed, but ignored");
509 // add in any needed <br>s, and remove any unneeded ones.
510 nsresult rv = InsertBRElementToEmptyListItemsAndTableCellsInRange(
511 TopLevelEditSubActionDataRef().mChangedRange->StartRef().AsRaw(),
512 TopLevelEditSubActionDataRef().mChangedRange->EndRef().AsRaw());
513 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
514 return NS_ERROR_EDITOR_DESTROYED;
516 NS_WARNING_ASSERTION(
517 NS_SUCCEEDED(rv),
518 "HTMLEditor::InsertBRElementToEmptyListItemsAndTableCellsInRange()"
519 " failed, but ignored");
521 // merge any adjacent text nodes
522 switch (GetTopLevelEditSubAction()) {
523 case EditSubAction::eInsertText:
524 case EditSubAction::eInsertTextComingFromIME:
525 break;
526 default: {
527 nsresult rv = CollapseAdjacentTextNodes(
528 MOZ_KnownLive(*TopLevelEditSubActionDataRef().mChangedRange));
529 if (NS_WARN_IF(Destroyed())) {
530 return NS_ERROR_EDITOR_DESTROYED;
532 if (NS_FAILED(rv)) {
533 NS_WARNING("HTMLEditor::CollapseAdjacentTextNodes() failed");
534 return rv;
536 break;
540 // Clean up any empty nodes in the changed range unless they are inserted
541 // intentionally.
542 if (TopLevelEditSubActionDataRef().mNeedsToCleanUpEmptyElements) {
543 nsresult rv = RemoveEmptyNodesIn(
544 EditorDOMRange(*TopLevelEditSubActionDataRef().mChangedRange));
545 if (NS_FAILED(rv)) {
546 NS_WARNING("HTMLEditor::RemoveEmptyNodesIn() failed");
547 return rv;
551 // attempt to transform any unneeded nbsp's into spaces after doing various
552 // operations
553 switch (GetTopLevelEditSubAction()) {
554 case EditSubAction::eDeleteSelectedContent:
555 if (TopLevelEditSubActionDataRef().mDidNormalizeWhitespaces) {
556 break;
558 [[fallthrough]];
559 case EditSubAction::eInsertText:
560 case EditSubAction::eInsertTextComingFromIME:
561 case EditSubAction::eInsertLineBreak:
562 case EditSubAction::eInsertParagraphSeparator:
563 case EditSubAction::ePasteHTMLContent:
564 case EditSubAction::eInsertHTMLSource: {
565 // Due to the replacement of white-spaces in
566 // WhiteSpaceVisibilityKeeper::NormalizeVisibleWhiteSpacesAt(),
567 // selection ranges may be changed since DOM ranges track the DOM
568 // mutation by themselves. However, we want to keep selection as-is.
569 // Therefore, we should restore `Selection` after replacing
570 // white-spaces.
571 AutoSelectionRestorer restoreSelection(*this);
572 // TODO: Temporarily, WhiteSpaceVisibilityKeeper replaces ASCII
573 // white-spaces with NPSPs and then, we'll replace them with ASCII
574 // white-spaces here. We should avoid this overwriting things as
575 // far as possible because replacing characters in text nodes
576 // causes running mutation event listeners which are really
577 // expensive.
578 // Adjust end of composition string if there is composition string.
579 auto pointToAdjust = GetLastIMESelectionEndPoint<EditorDOMPoint>();
580 if (!pointToAdjust.IsInContentNode()) {
581 // Otherwise, adjust current selection start point.
582 pointToAdjust = GetFirstSelectionStartPoint<EditorDOMPoint>();
583 if (NS_WARN_IF(!pointToAdjust.IsInContentNode())) {
584 return NS_ERROR_FAILURE;
587 if (EditorUtils::IsEditableContent(
588 *pointToAdjust.ContainerAs<nsIContent>(), EditorType::HTML)) {
589 AutoTrackDOMPoint trackPointToAdjust(RangeUpdaterRef(),
590 &pointToAdjust);
591 nsresult rv =
592 WhiteSpaceVisibilityKeeper::NormalizeVisibleWhiteSpacesAt(
593 *this, pointToAdjust);
594 if (NS_FAILED(rv)) {
595 NS_WARNING(
596 "WhiteSpaceVisibilityKeeper::NormalizeVisibleWhiteSpacesAt() "
597 "failed");
598 return rv;
602 // also do this for original selection endpoints.
603 // XXX Hmm, if `NormalizeVisibleWhiteSpacesAt()` runs mutation event
604 // listener and that causes changing `mSelectedRange`, what we
605 // should do?
606 if (NS_WARN_IF(!TopLevelEditSubActionDataRef()
607 .mSelectedRange->IsPositioned())) {
608 return NS_ERROR_FAILURE;
611 EditorDOMPoint atStart =
612 TopLevelEditSubActionDataRef().mSelectedRange->StartPoint();
613 if (atStart != pointToAdjust && atStart.IsInContentNode() &&
614 EditorUtils::IsEditableContent(*atStart.ContainerAs<nsIContent>(),
615 EditorType::HTML)) {
616 AutoTrackDOMPoint trackPointToAdjust(RangeUpdaterRef(),
617 &pointToAdjust);
618 AutoTrackDOMPoint trackStartPoint(RangeUpdaterRef(), &atStart);
619 nsresult rv =
620 WhiteSpaceVisibilityKeeper::NormalizeVisibleWhiteSpacesAt(
621 *this, atStart);
622 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
623 return NS_ERROR_EDITOR_DESTROYED;
625 NS_WARNING_ASSERTION(
626 NS_SUCCEEDED(rv),
627 "WhiteSpaceVisibilityKeeper::NormalizeVisibleWhiteSpacesAt() "
628 "failed, but ignored");
630 // we only need to handle old selection endpoint if it was different
631 // from start
632 EditorDOMPoint atEnd =
633 TopLevelEditSubActionDataRef().mSelectedRange->EndPoint();
634 if (!TopLevelEditSubActionDataRef().mSelectedRange->Collapsed() &&
635 atEnd != pointToAdjust && atEnd != atStart &&
636 atEnd.IsInContentNode() &&
637 EditorUtils::IsEditableContent(*atEnd.ContainerAs<nsIContent>(),
638 EditorType::HTML)) {
639 nsresult rv =
640 WhiteSpaceVisibilityKeeper::NormalizeVisibleWhiteSpacesAt(*this,
641 atEnd);
642 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
643 return NS_ERROR_EDITOR_DESTROYED;
645 NS_WARNING_ASSERTION(
646 NS_SUCCEEDED(rv),
647 "WhiteSpaceVisibilityKeeper::NormalizeVisibleWhiteSpacesAt() "
648 "failed, but ignored");
650 break;
652 default:
653 break;
656 // Adjust selection for insert text, html paste, and delete actions if
657 // we haven't removed new empty blocks. Note that if empty block parents
658 // are removed, Selection should've been adjusted by the method which
659 // did it.
660 if (!TopLevelEditSubActionDataRef().mDidDeleteEmptyParentBlocks &&
661 SelectionRef().IsCollapsed()) {
662 switch (GetTopLevelEditSubAction()) {
663 case EditSubAction::eInsertText:
664 case EditSubAction::eInsertTextComingFromIME:
665 case EditSubAction::eDeleteSelectedContent:
666 case EditSubAction::eInsertLineBreak:
667 case EditSubAction::eInsertParagraphSeparator:
668 case EditSubAction::ePasteHTMLContent:
669 case EditSubAction::eInsertHTMLSource:
670 // XXX AdjustCaretPositionAndEnsurePaddingBRElement() intentionally
671 // does not create padding `<br>` element for empty editor.
672 // Investigate which is better that whether this should does it
673 // or wait MaybeCreatePaddingBRElementForEmptyEditor().
674 rv = AdjustCaretPositionAndEnsurePaddingBRElement(
675 GetDirectionOfTopLevelEditSubAction());
676 if (NS_FAILED(rv)) {
677 NS_WARNING(
678 "HTMLEditor::AdjustCaretPositionAndEnsurePaddingBRElement() "
679 "failed");
680 return rv;
682 break;
683 default:
684 break;
688 // check for any styles which were removed inappropriately
689 bool reapplyCachedStyle;
690 switch (GetTopLevelEditSubAction()) {
691 case EditSubAction::eInsertText:
692 case EditSubAction::eInsertTextComingFromIME:
693 case EditSubAction::eDeleteSelectedContent:
694 reapplyCachedStyle = true;
695 break;
696 default:
697 reapplyCachedStyle =
698 IsPendingStyleCachePreservingSubAction(GetTopLevelEditSubAction());
699 break;
702 // If the selection is in empty inline HTML elements, we should delete
703 // them unless it's inserted intentionally.
704 if (mPlaceholderBatch &&
705 TopLevelEditSubActionDataRef().mNeedsToCleanUpEmptyElements &&
706 SelectionRef().IsCollapsed() && SelectionRef().GetFocusNode()) {
707 RefPtr<Element> mostDistantEmptyInlineAncestor = nullptr;
708 for (Element* ancestor :
709 SelectionRef().GetFocusNode()->InclusiveAncestorsOfType<Element>()) {
710 if (!ancestor->IsHTMLElement() ||
711 !HTMLEditUtils::IsRemovableFromParentNode(*ancestor) ||
712 !HTMLEditUtils::IsEmptyInlineContainer(
713 *ancestor, {EmptyCheckOption::TreatSingleBRElementAsVisible},
714 BlockInlineCheck::UseComputedDisplayStyle)) {
715 break;
717 mostDistantEmptyInlineAncestor = ancestor;
719 if (mostDistantEmptyInlineAncestor) {
720 nsresult rv =
721 DeleteNodeWithTransaction(*mostDistantEmptyInlineAncestor);
722 if (NS_FAILED(rv)) {
723 NS_WARNING(
724 "EditorBase::DeleteNodeWithTransaction() failed at deleting "
725 "empty inline ancestors");
726 return rv;
731 // But the cached inline styles should be restored from type-in-state later.
732 if (reapplyCachedStyle) {
733 DebugOnly<nsresult> rvIgnored =
734 mPendingStylesToApplyToNewContent->UpdateSelState(*this);
735 NS_WARNING_ASSERTION(
736 NS_SUCCEEDED(rvIgnored),
737 "PendingStyles::UpdateSelState() failed, but ignored");
738 rvIgnored = ReapplyCachedStyles();
739 NS_WARNING_ASSERTION(
740 NS_SUCCEEDED(rvIgnored),
741 "HTMLEditor::ReapplyCachedStyles() failed, but ignored");
742 TopLevelEditSubActionDataRef().mCachedPendingStyles->Clear();
746 rv = HandleInlineSpellCheck(
747 TopLevelEditSubActionDataRef().mSelectedRange->StartPoint(),
748 TopLevelEditSubActionDataRef().mChangedRange);
749 if (NS_FAILED(rv)) {
750 NS_WARNING("EditorBase::HandleInlineSpellCheck() failed");
751 return rv;
754 // detect empty doc
755 // XXX Need to investigate when the padding <br> element is removed because
756 // I don't see the <br> element with testing manually. If it won't be
757 // used, we can get rid of this cost.
758 rv = MaybeCreatePaddingBRElementForEmptyEditor();
759 if (NS_FAILED(rv)) {
760 NS_WARNING(
761 "EditorBase::MaybeCreatePaddingBRElementForEmptyEditor() failed");
762 return rv;
765 // adjust selection HINT if needed
766 if (!TopLevelEditSubActionDataRef().mDidExplicitlySetInterLine &&
767 SelectionRef().IsCollapsed()) {
768 SetSelectionInterlinePosition();
771 return NS_OK;
774 Result<EditActionResult, nsresult> HTMLEditor::CanHandleHTMLEditSubAction(
775 CheckSelectionInReplacedElement aCheckSelectionInReplacedElement
776 /* = CheckSelectionInReplacedElement::Yes */) const {
777 MOZ_ASSERT(IsEditActionDataAvailable());
779 if (NS_WARN_IF(Destroyed())) {
780 return Err(NS_ERROR_EDITOR_DESTROYED);
783 // If there is not selection ranges, we should ignore the result.
784 if (!SelectionRef().RangeCount()) {
785 return EditActionResult::CanceledResult();
788 const nsRange* range = SelectionRef().GetRangeAt(0);
789 nsINode* selStartNode = range->GetStartContainer();
790 if (NS_WARN_IF(!selStartNode) || NS_WARN_IF(!selStartNode->IsContent())) {
791 return Err(NS_ERROR_FAILURE);
794 if (!HTMLEditUtils::IsSimplyEditableNode(*selStartNode)) {
795 return EditActionResult::CanceledResult();
798 nsINode* selEndNode = range->GetEndContainer();
799 if (NS_WARN_IF(!selEndNode) || NS_WARN_IF(!selEndNode->IsContent())) {
800 return Err(NS_ERROR_FAILURE);
803 if (selStartNode == selEndNode) {
804 if (aCheckSelectionInReplacedElement ==
805 CheckSelectionInReplacedElement::Yes &&
806 HTMLEditUtils::IsNonEditableReplacedContent(
807 *selStartNode->AsContent())) {
808 return EditActionResult::CanceledResult();
810 return EditActionResult::IgnoredResult();
813 if (HTMLEditUtils::IsNonEditableReplacedContent(*selStartNode->AsContent()) ||
814 HTMLEditUtils::IsNonEditableReplacedContent(*selEndNode->AsContent())) {
815 return EditActionResult::CanceledResult();
818 if (!HTMLEditUtils::IsSimplyEditableNode(*selEndNode)) {
819 return EditActionResult::CanceledResult();
822 // If anchor node is in an HTML element which has inert attribute, we should
823 // do nothing.
824 // XXX HTMLEditor typically uses first range instead of anchor/focus range.
825 // Therefore, referring first range here is more reasonable than
826 // anchor/focus range of Selection.
827 nsIContent* const selAnchorContent = SelectionRef().GetDirection() == eDirNext
828 ? nsIContent::FromNode(selStartNode)
829 : nsIContent::FromNode(selEndNode);
830 if (selAnchorContent &&
831 HTMLEditUtils::ContentIsInert(*selAnchorContent->AsContent())) {
832 return EditActionResult::CanceledResult();
835 // XXX What does it mean the common ancestor is editable? I have no idea.
836 // It should be in same (active) editing host, and even if it's editable,
837 // there may be non-editable contents in the range.
838 nsINode* commonAncestor = range->GetClosestCommonInclusiveAncestor();
839 if (MOZ_UNLIKELY(!commonAncestor)) {
840 NS_WARNING(
841 "AbstractRange::GetClosestCommonInclusiveAncestor() returned nullptr");
842 return Err(NS_ERROR_FAILURE);
844 return HTMLEditUtils::IsSimplyEditableNode(*commonAncestor)
845 ? EditActionResult::IgnoredResult()
846 : EditActionResult::CanceledResult();
849 MOZ_CAN_RUN_SCRIPT static nsStaticAtom& MarginPropertyAtomForIndent(
850 nsIContent& aContent) {
851 nsAutoString direction;
852 DebugOnly<nsresult> rvIgnored = CSSEditUtils::GetComputedProperty(
853 aContent, *nsGkAtoms::direction, direction);
854 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
855 "CSSEditUtils::GetComputedProperty(nsGkAtoms::direction)"
856 " failed, but ignored");
857 return direction.EqualsLiteral("rtl") ? *nsGkAtoms::marginRight
858 : *nsGkAtoms::marginLeft;
861 nsresult HTMLEditor::EnsureCaretNotAfterInvisibleBRElement() {
862 MOZ_ASSERT(IsEditActionDataAvailable());
863 MOZ_ASSERT(SelectionRef().IsCollapsed());
865 // If we are after a padding `<br>` element for empty last line in the same
866 // block, then move selection to be before it
867 const nsRange* firstRange = SelectionRef().GetRangeAt(0);
868 if (NS_WARN_IF(!firstRange)) {
869 return NS_ERROR_FAILURE;
872 EditorRawDOMPoint atSelectionStart(firstRange->StartRef());
873 if (NS_WARN_IF(!atSelectionStart.IsSet())) {
874 return NS_ERROR_FAILURE;
876 MOZ_ASSERT(atSelectionStart.IsSetAndValid());
878 if (!atSelectionStart.IsInContentNode()) {
879 return NS_OK;
882 Element* editingHost = ComputeEditingHost();
883 if (!editingHost) {
884 NS_WARNING(
885 "HTMLEditor::EnsureCaretNotAfterInvisibleBRElement() did nothing "
886 "because of no editing host");
887 return NS_OK;
890 nsIContent* previousBRElement = HTMLEditUtils::GetPreviousContent(
891 atSelectionStart, {}, BlockInlineCheck::UseComputedDisplayStyle,
892 editingHost);
893 if (!previousBRElement || !previousBRElement->IsHTMLElement(nsGkAtoms::br) ||
894 !previousBRElement->GetParent() ||
895 !EditorUtils::IsEditableContent(*previousBRElement->GetParent(),
896 EditorType::HTML) ||
897 !HTMLEditUtils::IsInvisibleBRElement(*previousBRElement)) {
898 return NS_OK;
901 const RefPtr<const Element> blockElementAtSelectionStart =
902 HTMLEditUtils::GetInclusiveAncestorElement(
903 *atSelectionStart.ContainerAs<nsIContent>(),
904 HTMLEditUtils::ClosestBlockElement,
905 BlockInlineCheck::UseComputedDisplayStyle);
906 const RefPtr<const Element> parentBlockElementOfBRElement =
907 HTMLEditUtils::GetAncestorElement(
908 *previousBRElement, HTMLEditUtils::ClosestBlockElement,
909 BlockInlineCheck::UseComputedDisplayStyle);
911 if (!blockElementAtSelectionStart ||
912 blockElementAtSelectionStart != parentBlockElementOfBRElement) {
913 return NS_OK;
916 // If we are here then the selection is right after a padding <br>
917 // element for empty last line that is in the same block as the
918 // selection. We need to move the selection start to be before the
919 // padding <br> element.
920 EditorRawDOMPoint atInvisibleBRElement(previousBRElement);
921 nsresult rv = CollapseSelectionTo(atInvisibleBRElement);
922 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
923 "EditorBase::CollapseSelectionTo() failed");
924 return rv;
927 nsresult HTMLEditor::MaybeCreatePaddingBRElementForEmptyEditor() {
928 MOZ_ASSERT(IsEditActionDataAvailable());
930 if (mPaddingBRElementForEmptyEditor) {
931 return NS_OK;
934 // XXX I think that we should not insert a <br> element if we're for a web
935 // content. Probably, this is required only by chrome editors such as
936 // the mail composer of Thunderbird and the composer of SeaMonkey.
938 const RefPtr<Element> bodyOrDocumentElement = GetRoot();
939 if (!bodyOrDocumentElement) {
940 return NS_OK;
943 // Skip adding the padding <br> element for empty editor if body
944 // is read-only.
945 if (!HTMLEditUtils::IsSimplyEditableNode(*bodyOrDocumentElement)) {
946 return NS_OK;
949 // Now we've got the body element. Iterate over the body element's children,
950 // looking for editable content. If no editable content is found, insert the
951 // padding <br> element.
952 EditorType editorType = GetEditorType();
953 bool isRootEditable =
954 EditorUtils::IsEditableContent(*bodyOrDocumentElement, editorType);
955 for (nsIContent* child = bodyOrDocumentElement->GetFirstChild(); child;
956 child = child->GetNextSibling()) {
957 if (EditorUtils::IsPaddingBRElementForEmptyEditor(*child) ||
958 !isRootEditable || EditorUtils::IsEditableContent(*child, editorType) ||
959 HTMLEditUtils::IsBlockElement(
960 *child, BlockInlineCheck::UseComputedDisplayStyle)) {
961 return NS_OK;
965 IgnoredErrorResult ignoredError;
966 AutoEditSubActionNotifier startToHandleEditSubAction(
967 *this, EditSubAction::eCreatePaddingBRElementForEmptyEditor,
968 nsIEditor::eNone, ignoredError);
969 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
970 return ignoredError.StealNSResult();
972 NS_WARNING_ASSERTION(
973 !ignoredError.Failed(),
974 "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
976 // Create a br.
977 RefPtr<Element> newBRElement = CreateHTMLContent(nsGkAtoms::br);
978 if (NS_WARN_IF(Destroyed())) {
979 return NS_ERROR_EDITOR_DESTROYED;
981 if (NS_WARN_IF(!newBRElement)) {
982 return NS_ERROR_FAILURE;
985 mPaddingBRElementForEmptyEditor =
986 static_cast<HTMLBRElement*>(newBRElement.get());
988 // Give it a special attribute.
989 newBRElement->SetFlags(NS_PADDING_FOR_EMPTY_EDITOR);
991 // Put the node in the document.
992 Result<CreateElementResult, nsresult> insertBRElementResult =
993 InsertNodeWithTransaction<Element>(
994 *newBRElement, EditorDOMPoint(bodyOrDocumentElement, 0u));
995 if (MOZ_UNLIKELY(insertBRElementResult.isErr())) {
996 NS_WARNING("EditorBase::InsertNodeWithTransaction() failed");
997 return insertBRElementResult.unwrapErr();
1000 // Set selection.
1001 insertBRElementResult.inspect().IgnoreCaretPointSuggestion();
1002 nsresult rv = CollapseSelectionToStartOf(*bodyOrDocumentElement);
1003 if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) {
1004 NS_WARNING(
1005 "EditorBase::CollapseSelectionToStartOf() caused destroying the "
1006 "editor");
1007 return NS_ERROR_EDITOR_DESTROYED;
1009 NS_WARNING_ASSERTION(
1010 NS_SUCCEEDED(rv),
1011 "EditorBase::CollapseSelectionToStartOf() failed, but ignored");
1012 return NS_OK;
1015 nsresult HTMLEditor::EnsureNoPaddingBRElementForEmptyEditor() {
1016 MOZ_ASSERT(IsEditActionDataAvailable());
1018 if (!mPaddingBRElementForEmptyEditor) {
1019 return NS_OK;
1022 // If we're an HTML editor, a mutation event listener may recreate padding
1023 // <br> element for empty editor again during the call of
1024 // DeleteNodeWithTransaction(). So, move it first.
1025 RefPtr<HTMLBRElement> paddingBRElement(
1026 std::move(mPaddingBRElementForEmptyEditor));
1027 nsresult rv = DeleteNodeWithTransaction(*paddingBRElement);
1028 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
1029 "EditorBase::DeleteNodeWithTransaction() failed");
1030 return rv;
1033 nsresult HTMLEditor::ReflectPaddingBRElementForEmptyEditor() {
1034 if (NS_WARN_IF(!mRootElement)) {
1035 NS_WARNING("Failed to handle padding BR element due to no root element");
1036 return NS_ERROR_FAILURE;
1038 // The idea here is to see if the magic empty node has suddenly reappeared. If
1039 // it has, set our state so we remember it. There is a tradeoff between doing
1040 // here and at redo, or doing it everywhere else that might care. Since undo
1041 // and redo are relatively rare, it makes sense to take the (small)
1042 // performance hit here.
1043 nsIContent* firstLeafChild = HTMLEditUtils::GetFirstLeafContent(
1044 *mRootElement, {LeafNodeType::OnlyLeafNode});
1045 if (firstLeafChild &&
1046 EditorUtils::IsPaddingBRElementForEmptyEditor(*firstLeafChild)) {
1047 mPaddingBRElementForEmptyEditor =
1048 static_cast<HTMLBRElement*>(firstLeafChild);
1049 } else {
1050 mPaddingBRElementForEmptyEditor = nullptr;
1052 return NS_OK;
1055 nsresult HTMLEditor::PrepareInlineStylesForCaret() {
1056 MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable());
1057 MOZ_ASSERT(SelectionRef().IsCollapsed());
1059 // XXX This method works with the top level edit sub-action, but this
1060 // must be wrong if we are handling nested edit action.
1062 if (TopLevelEditSubActionDataRef().mDidDeleteSelection) {
1063 switch (GetTopLevelEditSubAction()) {
1064 case EditSubAction::eInsertText:
1065 case EditSubAction::eInsertTextComingFromIME:
1066 case EditSubAction::eDeleteSelectedContent: {
1067 nsresult rv = ReapplyCachedStyles();
1068 if (NS_FAILED(rv)) {
1069 NS_WARNING("HTMLEditor::ReapplyCachedStyles() failed");
1070 return rv;
1072 break;
1074 default:
1075 break;
1078 // For most actions we want to clear the cached styles, but there are
1079 // exceptions
1080 if (!IsPendingStyleCachePreservingSubAction(GetTopLevelEditSubAction())) {
1081 TopLevelEditSubActionDataRef().mCachedPendingStyles->Clear();
1083 return NS_OK;
1086 Result<EditActionResult, nsresult> HTMLEditor::HandleInsertText(
1087 EditSubAction aEditSubAction, const nsAString& aInsertionString,
1088 SelectionHandling aSelectionHandling) {
1089 MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable());
1090 MOZ_ASSERT(aEditSubAction == EditSubAction::eInsertText ||
1091 aEditSubAction == EditSubAction::eInsertTextComingFromIME);
1092 MOZ_ASSERT_IF(aSelectionHandling == SelectionHandling::Ignore,
1093 aEditSubAction == EditSubAction::eInsertTextComingFromIME);
1096 Result<EditActionResult, nsresult> result = CanHandleHTMLEditSubAction();
1097 if (MOZ_UNLIKELY(result.isErr())) {
1098 NS_WARNING("HTMLEditor::CanHandleHTMLEditSubAction() failed");
1099 return result;
1101 if (result.inspect().Canceled()) {
1102 return result;
1106 UndefineCaretBidiLevel();
1108 // If the selection isn't collapsed, delete it. Don't delete existing inline
1109 // tags, because we're hopefully going to insert text (bug 787432).
1110 if (!SelectionRef().IsCollapsed() &&
1111 aSelectionHandling == SelectionHandling::Delete) {
1112 nsresult rv =
1113 DeleteSelectionAsSubAction(nsIEditor::eNone, nsIEditor::eNoStrip);
1114 if (NS_FAILED(rv)) {
1115 NS_WARNING(
1116 "EditorBase::DeleteSelectionAsSubAction(nsIEditor::eNone, "
1117 "nsIEditor::eNoStrip) failed");
1118 return Err(rv);
1122 nsresult rv = EnsureNoPaddingBRElementForEmptyEditor();
1123 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
1124 return Err(NS_ERROR_EDITOR_DESTROYED);
1126 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
1127 "EditorBase::EnsureNoPaddingBRElementForEmptyEditor() "
1128 "failed, but ignored");
1130 if (NS_SUCCEEDED(rv) && SelectionRef().IsCollapsed()) {
1131 nsresult rv = EnsureCaretNotAfterInvisibleBRElement();
1132 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
1133 return Err(NS_ERROR_EDITOR_DESTROYED);
1135 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
1136 "HTMLEditor::EnsureCaretNotAfterInvisibleBRElement() "
1137 "failed, but ignored");
1138 if (NS_SUCCEEDED(rv)) {
1139 nsresult rv = PrepareInlineStylesForCaret();
1140 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
1141 return Err(NS_ERROR_EDITOR_DESTROYED);
1143 NS_WARNING_ASSERTION(
1144 NS_SUCCEEDED(rv),
1145 "HTMLEditor::PrepareInlineStylesForCaret() failed, but ignored");
1149 RefPtr<Document> document = GetDocument();
1150 if (NS_WARN_IF(!document)) {
1151 return Err(NS_ERROR_FAILURE);
1154 const RefPtr<Element> editingHost = ComputeEditingHost(
1155 GetDocument()->IsXMLDocument() ? LimitInBodyElement::No
1156 : LimitInBodyElement::Yes);
1157 if (NS_WARN_IF(!editingHost)) {
1158 return Err(NS_ERROR_FAILURE);
1161 auto pointToInsert = GetFirstSelectionStartPoint<EditorDOMPoint>();
1162 if (MOZ_UNLIKELY(!pointToInsert.IsSet())) {
1163 return Err(NS_ERROR_FAILURE);
1166 // for every property that is set, insert a new inline style node
1167 Result<EditorDOMPoint, nsresult> setStyleResult =
1168 CreateStyleForInsertText(pointToInsert, *editingHost);
1169 if (MOZ_UNLIKELY(setStyleResult.isErr())) {
1170 NS_WARNING("HTMLEditor::CreateStyleForInsertText() failed");
1171 return setStyleResult.propagateErr();
1173 if (setStyleResult.inspect().IsSet()) {
1174 pointToInsert = setStyleResult.unwrap();
1177 if (NS_WARN_IF(!pointToInsert.IsSetAndValid()) ||
1178 NS_WARN_IF(!pointToInsert.IsInContentNode())) {
1179 return Err(NS_ERROR_FAILURE);
1181 MOZ_ASSERT(pointToInsert.IsSetAndValid());
1183 // If the point is not in an element which can contain text nodes, climb up
1184 // the DOM tree.
1185 if (!pointToInsert.IsInTextNode()) {
1186 while (!HTMLEditUtils::CanNodeContain(*pointToInsert.GetContainer(),
1187 *nsGkAtoms::textTagName)) {
1188 if (NS_WARN_IF(pointToInsert.GetContainer() == editingHost) ||
1189 NS_WARN_IF(!pointToInsert.GetContainerParentAs<nsIContent>())) {
1190 NS_WARNING("Selection start point couldn't have text nodes");
1191 return Err(NS_ERROR_FAILURE);
1193 pointToInsert.Set(pointToInsert.ContainerAs<nsIContent>());
1197 if (aEditSubAction == EditSubAction::eInsertTextComingFromIME) {
1198 auto compositionStartPoint =
1199 GetFirstIMESelectionStartPoint<EditorDOMPoint>();
1200 if (!compositionStartPoint.IsSet()) {
1201 compositionStartPoint = pointToInsert;
1204 if (aInsertionString.IsEmpty()) {
1205 // Right now the WhiteSpaceVisibilityKeeper code bails on empty strings,
1206 // but IME needs the InsertTextWithTransaction() call to still happen
1207 // since empty strings are meaningful there.
1208 Result<InsertTextResult, nsresult> insertTextResult =
1209 InsertTextWithTransaction(*document, aInsertionString,
1210 compositionStartPoint);
1211 if (MOZ_UNLIKELY(insertTextResult.isErr())) {
1212 NS_WARNING("HTMLEditor::InsertTextWithTransaction() failed");
1213 return insertTextResult.propagateErr();
1215 nsresult rv = insertTextResult.unwrap().SuggestCaretPointTo(
1216 *this, {SuggestCaret::OnlyIfHasSuggestion,
1217 SuggestCaret::OnlyIfTransactionsAllowedToDoIt,
1218 SuggestCaret::AndIgnoreTrivialError});
1219 if (NS_FAILED(rv)) {
1220 NS_WARNING("CaretPoint::SuggestCaretPointTo() failed");
1221 return Err(rv);
1223 NS_WARNING_ASSERTION(
1224 rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
1225 "CaretPoint::SuggestCaretPointTo() failed, but ignored");
1226 return EditActionResult::HandledResult();
1229 auto compositionEndPoint = GetLastIMESelectionEndPoint<EditorDOMPoint>();
1230 if (!compositionEndPoint.IsSet()) {
1231 compositionEndPoint = compositionStartPoint;
1233 Result<InsertTextResult, nsresult> replaceTextResult =
1234 WhiteSpaceVisibilityKeeper::ReplaceText(
1235 *this, aInsertionString,
1236 EditorDOMRange(compositionStartPoint, compositionEndPoint),
1237 *editingHost);
1238 if (MOZ_UNLIKELY(replaceTextResult.isErr())) {
1239 NS_WARNING("WhiteSpaceVisibilityKeeper::ReplaceText() failed");
1240 return replaceTextResult.propagateErr();
1242 // CompositionTransaction should've set selection so that we should ignore
1243 // caret suggestion.
1244 replaceTextResult.unwrap().IgnoreCaretPointSuggestion();
1246 compositionStartPoint = GetFirstIMESelectionStartPoint<EditorDOMPoint>();
1247 compositionEndPoint = GetLastIMESelectionEndPoint<EditorDOMPoint>();
1248 if (NS_WARN_IF(!compositionStartPoint.IsSet()) ||
1249 NS_WARN_IF(!compositionEndPoint.IsSet())) {
1250 // Mutation event listener has changed the DOM tree...
1251 return EditActionResult::HandledResult();
1253 nsresult rv = TopLevelEditSubActionDataRef().mChangedRange->SetStartAndEnd(
1254 compositionStartPoint.ToRawRangeBoundary(),
1255 compositionEndPoint.ToRawRangeBoundary());
1256 if (NS_FAILED(rv)) {
1257 NS_WARNING("nsRange::SetStartAndEnd() failed");
1258 return Err(rv);
1260 return EditActionResult::HandledResult();
1263 MOZ_ASSERT(aEditSubAction == EditSubAction::eInsertText);
1265 // find where we are
1266 EditorDOMPoint currentPoint(pointToInsert);
1268 // is our text going to be PREformatted?
1269 // We remember this so that we know how to handle tabs.
1270 const bool isWhiteSpaceCollapsible = !EditorUtils::IsWhiteSpacePreformatted(
1271 *pointToInsert.ContainerAs<nsIContent>());
1273 // turn off the edit listener: we know how to
1274 // build the "doc changed range" ourselves, and it's
1275 // must faster to do it once here than to track all
1276 // the changes one at a time.
1277 AutoRestore<bool> disableListener(
1278 EditSubActionDataRef().mAdjustChangedRangeFromListener);
1279 EditSubActionDataRef().mAdjustChangedRangeFromListener = false;
1281 // don't change my selection in subtransactions
1282 AutoTransactionsConserveSelection dontChangeMySelection(*this);
1283 int32_t pos = 0;
1284 constexpr auto newlineStr = NS_LITERAL_STRING_FROM_CSTRING(LFSTR);
1287 AutoTrackDOMPoint tracker(RangeUpdaterRef(), &pointToInsert);
1289 // for efficiency, break out the pre case separately. This is because
1290 // its a lot cheaper to search the input string for only newlines than
1291 // it is to search for both tabs and newlines.
1292 if (!isWhiteSpaceCollapsible || IsPlaintextMailComposer()) {
1293 while (pos != -1 &&
1294 pos < AssertedCast<int32_t>(aInsertionString.Length())) {
1295 int32_t oldPos = pos;
1296 int32_t subStrLen;
1297 pos = aInsertionString.FindChar(nsCRT::LF, oldPos);
1299 if (pos != -1) {
1300 subStrLen = pos - oldPos;
1301 // if first char is newline, then use just it
1302 if (!subStrLen) {
1303 subStrLen = 1;
1305 } else {
1306 subStrLen = aInsertionString.Length() - oldPos;
1307 pos = aInsertionString.Length();
1310 nsDependentSubstring subStr(aInsertionString, oldPos, subStrLen);
1312 // is it a return?
1313 if (subStr.Equals(newlineStr)) {
1314 Result<CreateElementResult, nsresult> insertBRElementResult =
1315 InsertBRElement(WithTransaction::Yes, currentPoint);
1316 if (MOZ_UNLIKELY(insertBRElementResult.isErr())) {
1317 NS_WARNING(
1318 "HTMLEditor::InsertBRElement(WithTransaction::Yes) failed");
1319 return insertBRElementResult.propagateErr();
1321 CreateElementResult unwrappedInsertBRElementResult =
1322 insertBRElementResult.unwrap();
1323 // We don't want to update selection here because we've blocked
1324 // InsertNodeTransaction updating selection with
1325 // dontChangeMySelection.
1326 unwrappedInsertBRElementResult.IgnoreCaretPointSuggestion();
1327 MOZ_ASSERT(!AllowsTransactionsToChangeSelection());
1329 pos++;
1330 RefPtr<Element> brElement =
1331 unwrappedInsertBRElementResult.UnwrapNewNode();
1332 if (brElement->GetNextSibling()) {
1333 pointToInsert.Set(brElement->GetNextSibling());
1334 } else {
1335 pointToInsert.SetToEndOf(currentPoint.GetContainer());
1337 // XXX In most cases, pointToInsert and currentPoint are same here.
1338 // But if the <br> element has been moved to different point by
1339 // mutation observer, those points become different.
1340 currentPoint.SetAfter(brElement);
1341 NS_WARNING_ASSERTION(currentPoint.IsSet(),
1342 "Failed to set after the <br> element");
1343 NS_WARNING_ASSERTION(currentPoint == pointToInsert,
1344 "Perhaps, <br> element position has been moved "
1345 "to different point "
1346 "by mutation observer");
1347 } else {
1348 Result<InsertTextResult, nsresult> insertTextResult =
1349 InsertTextWithTransaction(*document, subStr, currentPoint);
1350 if (MOZ_UNLIKELY(insertTextResult.isErr())) {
1351 NS_WARNING("HTMLEditor::InsertTextWithTransaction() failed");
1352 return insertTextResult.propagateErr();
1354 // Ignore the caret suggestion because of `dontChangeMySelection`
1355 // above.
1356 insertTextResult.inspect().IgnoreCaretPointSuggestion();
1357 if (insertTextResult.inspect().Handled()) {
1358 pointToInsert = currentPoint = insertTextResult.unwrap()
1359 .EndOfInsertedTextRef()
1360 .To<EditorDOMPoint>();
1361 } else {
1362 pointToInsert = currentPoint;
1366 } else {
1367 constexpr auto tabStr = u"\t"_ns;
1368 constexpr auto spacesStr = u" "_ns;
1369 nsAutoString insertionString(aInsertionString); // For FindCharInSet().
1370 while (pos != -1 &&
1371 pos < AssertedCast<int32_t>(insertionString.Length())) {
1372 int32_t oldPos = pos;
1373 int32_t subStrLen;
1374 pos = insertionString.FindCharInSet(u"\t\n", oldPos);
1376 if (pos != -1) {
1377 subStrLen = pos - oldPos;
1378 // if first char is newline, then use just it
1379 if (!subStrLen) {
1380 subStrLen = 1;
1382 } else {
1383 subStrLen = insertionString.Length() - oldPos;
1384 pos = insertionString.Length();
1387 nsDependentSubstring subStr(insertionString, oldPos, subStrLen);
1389 // is it a tab?
1390 if (subStr.Equals(tabStr)) {
1391 Result<InsertTextResult, nsresult> insertTextResult =
1392 WhiteSpaceVisibilityKeeper::InsertText(
1393 *this, spacesStr, currentPoint, *editingHost);
1394 if (MOZ_UNLIKELY(insertTextResult.isErr())) {
1395 NS_WARNING("WhiteSpaceVisibilityKeeper::InsertText() failed");
1396 return insertTextResult.propagateErr();
1398 // Ignore the caret suggestion because of `dontChangeMySelection`
1399 // above.
1400 insertTextResult.inspect().IgnoreCaretPointSuggestion();
1401 pos++;
1402 if (insertTextResult.inspect().Handled()) {
1403 pointToInsert = currentPoint = insertTextResult.unwrap()
1404 .EndOfInsertedTextRef()
1405 .To<EditorDOMPoint>();
1406 MOZ_ASSERT(pointToInsert.IsSet());
1407 } else {
1408 pointToInsert = currentPoint;
1409 MOZ_ASSERT(pointToInsert.IsSet());
1412 // is it a return?
1413 else if (subStr.Equals(newlineStr)) {
1414 Result<CreateElementResult, nsresult> insertBRElementResult =
1415 WhiteSpaceVisibilityKeeper::InsertBRElement(*this, currentPoint,
1416 *editingHost);
1417 if (MOZ_UNLIKELY(insertBRElementResult.isErr())) {
1418 NS_WARNING("WhiteSpaceVisibilityKeeper::InsertBRElement() failed");
1419 return insertBRElementResult.propagateErr();
1421 CreateElementResult unwrappedInsertBRElementResult =
1422 insertBRElementResult.unwrap();
1423 // TODO: Some methods called for handling non-preformatted text use
1424 // ComputeEditingHost(). Therefore, they depend on the latest
1425 // selection. So we cannot skip updating selection here.
1426 nsresult rv = unwrappedInsertBRElementResult.SuggestCaretPointTo(
1427 *this, {SuggestCaret::OnlyIfHasSuggestion,
1428 SuggestCaret::OnlyIfTransactionsAllowedToDoIt,
1429 SuggestCaret::AndIgnoreTrivialError});
1430 if (NS_FAILED(rv)) {
1431 NS_WARNING("CreateElementResult::SuggestCaretPointTo() failed");
1432 return Err(rv);
1434 NS_WARNING_ASSERTION(
1435 rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
1436 "CreateElementResult::SuggestCaretPointTo() failed, but ignored");
1437 pos++;
1438 RefPtr<Element> newBRElement =
1439 unwrappedInsertBRElementResult.UnwrapNewNode();
1440 MOZ_DIAGNOSTIC_ASSERT(newBRElement);
1441 if (newBRElement->GetNextSibling()) {
1442 pointToInsert.Set(newBRElement->GetNextSibling());
1443 } else {
1444 pointToInsert.SetToEndOf(currentPoint.GetContainer());
1446 currentPoint.SetAfter(newBRElement);
1447 NS_WARNING_ASSERTION(currentPoint.IsSet(),
1448 "Failed to set after the new <br> element");
1449 // XXX If the newBRElement has been moved or removed by mutation
1450 // observer, we hit this assert. We need to check if
1451 // newBRElement is in expected point, though, we must have
1452 // a lot of same bugs...
1453 NS_WARNING_ASSERTION(
1454 currentPoint == pointToInsert,
1455 "Perhaps, newBRElement has been moved or removed unexpectedly");
1456 } else {
1457 Result<InsertTextResult, nsresult> insertTextResult =
1458 WhiteSpaceVisibilityKeeper::InsertText(
1459 *this, subStr, currentPoint, *editingHost);
1460 if (MOZ_UNLIKELY(insertTextResult.isErr())) {
1461 NS_WARNING("WhiteSpaceVisibilityKeeper::InsertText() failed");
1462 return insertTextResult.propagateErr();
1464 // Ignore the caret suggestion because of `dontChangeMySelection`
1465 // above.
1466 insertTextResult.inspect().IgnoreCaretPointSuggestion();
1467 if (insertTextResult.inspect().Handled()) {
1468 pointToInsert = currentPoint = insertTextResult.unwrap()
1469 .EndOfInsertedTextRef()
1470 .To<EditorDOMPoint>();
1471 MOZ_ASSERT(pointToInsert.IsSet());
1472 } else {
1473 pointToInsert = currentPoint;
1474 MOZ_ASSERT(pointToInsert.IsSet());
1480 // After this block, pointToInsert is updated by AutoTrackDOMPoint.
1483 if (currentPoint.IsSet()) {
1484 currentPoint.SetInterlinePosition(InterlinePosition::EndOfLine);
1485 nsresult rv = CollapseSelectionTo(currentPoint);
1486 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
1487 return Err(NS_ERROR_EDITOR_DESTROYED);
1489 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
1490 "Selection::Collapse() failed, but ignored");
1492 // manually update the doc changed range so that AfterEdit will clean up
1493 // the correct portion of the document.
1494 rv = TopLevelEditSubActionDataRef().mChangedRange->SetStartAndEnd(
1495 pointToInsert.ToRawRangeBoundary(), currentPoint.ToRawRangeBoundary());
1496 if (NS_FAILED(rv)) {
1497 NS_WARNING("nsRange::SetStartAndEnd() failed");
1498 return Err(rv);
1500 return EditActionResult::HandledResult();
1503 DebugOnly<nsresult> rvIgnored =
1504 SelectionRef().SetInterlinePosition(InterlinePosition::EndOfLine);
1505 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
1506 "Selection::SetInterlinePosition(InterlinePosition::"
1507 "EndOfLine) failed, but ignored");
1508 rv = TopLevelEditSubActionDataRef().mChangedRange->CollapseTo(pointToInsert);
1509 if (NS_FAILED(rv)) {
1510 NS_WARNING("nsRange::CollapseTo() failed");
1511 return Err(rv);
1513 return EditActionResult::HandledResult();
1516 nsresult HTMLEditor::InsertLineBreakAsSubAction() {
1517 MOZ_ASSERT(IsEditActionDataAvailable());
1518 MOZ_ASSERT(!IsSelectionRangeContainerNotContent());
1520 if (NS_WARN_IF(!mInitSucceeded)) {
1521 return NS_ERROR_NOT_INITIALIZED;
1525 Result<EditActionResult, nsresult> result = CanHandleHTMLEditSubAction();
1526 if (MOZ_UNLIKELY(result.isErr())) {
1527 NS_WARNING("HTMLEditor::CanHandleHTMLEditSubAction() failed");
1528 return result.unwrapErr();
1530 if (result.inspect().Canceled()) {
1531 return NS_OK;
1535 // XXX This may be called by execCommand() with "insertLineBreak".
1536 // In such case, naming the transaction "TypingTxnName" is odd.
1537 AutoPlaceholderBatch treatAsOneTransaction(*this, *nsGkAtoms::TypingTxnName,
1538 ScrollSelectionIntoView::Yes,
1539 __FUNCTION__);
1541 // calling it text insertion to trigger moz br treatment by rules
1542 // XXX Why do we use EditSubAction::eInsertText here? Looks like
1543 // EditSubAction::eInsertLineBreak or EditSubAction::eInsertNode
1544 // is better.
1545 IgnoredErrorResult ignoredError;
1546 AutoEditSubActionNotifier startToHandleEditSubAction(
1547 *this, EditSubAction::eInsertText, nsIEditor::eNext, ignoredError);
1548 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
1549 return ignoredError.StealNSResult();
1551 NS_WARNING_ASSERTION(
1552 !ignoredError.Failed(),
1553 "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
1555 UndefineCaretBidiLevel();
1557 // If the selection isn't collapsed, delete it.
1558 if (!SelectionRef().IsCollapsed()) {
1559 nsresult rv =
1560 DeleteSelectionAsSubAction(nsIEditor::eNone, nsIEditor::eStrip);
1561 if (NS_FAILED(rv)) {
1562 NS_WARNING(
1563 "EditorBase::DeleteSelectionAsSubAction(eNone, eStrip) failed");
1564 return rv;
1568 const nsRange* firstRange = SelectionRef().GetRangeAt(0);
1569 if (NS_WARN_IF(!firstRange)) {
1570 return NS_ERROR_FAILURE;
1573 EditorDOMPoint atStartOfSelection(firstRange->StartRef());
1574 if (NS_WARN_IF(!atStartOfSelection.IsSet())) {
1575 return NS_ERROR_FAILURE;
1577 MOZ_ASSERT(atStartOfSelection.IsSetAndValid());
1579 RefPtr<Element> editingHost = ComputeEditingHost();
1580 if (NS_WARN_IF(!editingHost)) {
1581 return NS_ERROR_FAILURE;
1584 // For backward compatibility, we should not insert a linefeed if
1585 // paragraph separator is set to "br" which is Gecko-specific mode.
1586 if (GetDefaultParagraphSeparator() == ParagraphSeparator::br ||
1587 !HTMLEditUtils::ShouldInsertLinefeedCharacter(atStartOfSelection,
1588 *editingHost)) {
1589 Result<CreateElementResult, nsresult> insertBRElementResult =
1590 InsertBRElement(WithTransaction::Yes, atStartOfSelection,
1591 nsIEditor::eNext);
1592 if (MOZ_UNLIKELY(insertBRElementResult.isErr())) {
1593 NS_WARNING("HTMLEditor::InsertBRElement(WithTransaction::Yes) failed");
1594 return insertBRElementResult.unwrapErr();
1596 CreateElementResult unwrappedInsertBRElementResult =
1597 insertBRElementResult.unwrap();
1598 MOZ_ASSERT(unwrappedInsertBRElementResult.GetNewNode());
1599 unwrappedInsertBRElementResult.IgnoreCaretPointSuggestion();
1601 auto pointToPutCaret =
1602 EditorDOMPoint::After(*unwrappedInsertBRElementResult.GetNewNode());
1603 if (MOZ_UNLIKELY(!pointToPutCaret.IsSet())) {
1604 NS_WARNING("Inserted <br> was unexpectedly removed");
1605 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
1607 WSScanResult backwardScanFromBeforeBRElementResult =
1608 WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary(
1609 editingHost,
1610 EditorDOMPoint(unwrappedInsertBRElementResult.GetNewNode()),
1611 BlockInlineCheck::UseComputedDisplayStyle);
1612 if (MOZ_UNLIKELY(backwardScanFromBeforeBRElementResult.Failed())) {
1613 NS_WARNING(
1614 "WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary() failed");
1615 return Err(NS_ERROR_FAILURE);
1618 WSScanResult forwardScanFromAfterBRElementResult =
1619 WSRunScanner::ScanNextVisibleNodeOrBlockBoundary(
1620 editingHost, pointToPutCaret,
1621 BlockInlineCheck::UseComputedDisplayStyle);
1622 if (MOZ_UNLIKELY(forwardScanFromAfterBRElementResult.Failed())) {
1623 NS_WARNING("WSRunScanner::ScanNextVisibleNodeOrBlockBoundary() failed");
1624 return Err(NS_ERROR_FAILURE);
1626 const bool brElementIsAfterBlock =
1627 backwardScanFromBeforeBRElementResult.ReachedBlockBoundary();
1628 const bool brElementIsBeforeBlock =
1629 forwardScanFromAfterBRElementResult.ReachedBlockBoundary();
1630 const bool isEmptyEditingHost = HTMLEditUtils::IsEmptyNode(
1631 *editingHost, {EmptyCheckOption::TreatNonEditableContentAsInvisible});
1632 if (brElementIsBeforeBlock &&
1633 (isEmptyEditingHost || !brElementIsAfterBlock)) {
1634 // Empty last line is invisible if it's immediately before either parent
1635 // or another block's boundary so that we need to put invisible <br>
1636 // element here for making it visible.
1637 Result<CreateElementResult, nsresult> invisibleAdditionalBRElementResult =
1638 WhiteSpaceVisibilityKeeper::InsertBRElement(*this, pointToPutCaret,
1639 *editingHost);
1640 if (MOZ_UNLIKELY(invisibleAdditionalBRElementResult.isErr())) {
1641 NS_WARNING("WhiteSpaceVisibilityKeeper::InsertBRElement() failed");
1642 return invisibleAdditionalBRElementResult.unwrapErr();
1644 CreateElementResult unwrappedInvisibleAdditionalBRElement =
1645 invisibleAdditionalBRElementResult.unwrap();
1646 pointToPutCaret.Set(unwrappedInvisibleAdditionalBRElement.GetNewNode());
1647 unwrappedInvisibleAdditionalBRElement.IgnoreCaretPointSuggestion();
1648 } else if (forwardScanFromAfterBRElementResult
1649 .InVisibleOrCollapsibleCharacters()) {
1650 pointToPutCaret =
1651 forwardScanFromAfterBRElementResult.Point<EditorDOMPoint>();
1652 } else if (forwardScanFromAfterBRElementResult.ReachedSpecialContent()) {
1653 // Next inserting text should be inserted into styled inline elements if
1654 // they have first visible thing in the new line.
1655 pointToPutCaret =
1656 forwardScanFromAfterBRElementResult.PointAtContent<EditorDOMPoint>();
1659 nsresult rv = CollapseSelectionTo(pointToPutCaret);
1660 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
1661 "CreateElementResult::SuggestCaretPointTo() failed");
1662 return rv;
1665 nsresult rv = EnsureNoPaddingBRElementForEmptyEditor();
1666 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
1667 return NS_ERROR_EDITOR_DESTROYED;
1669 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
1670 "EditorBase::EnsureNoPaddingBRElementForEmptyEditor() "
1671 "failed, but ignored");
1673 if (NS_SUCCEEDED(rv) && SelectionRef().IsCollapsed()) {
1674 nsresult rv = EnsureCaretNotAfterInvisibleBRElement();
1675 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
1676 return NS_ERROR_EDITOR_DESTROYED;
1678 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
1679 "HTMLEditor::EnsureCaretNotAfterInvisibleBRElement() "
1680 "failed, but ignored");
1681 if (NS_SUCCEEDED(rv)) {
1682 nsresult rv = PrepareInlineStylesForCaret();
1683 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
1684 return NS_ERROR_EDITOR_DESTROYED;
1686 NS_WARNING_ASSERTION(
1687 NS_SUCCEEDED(rv),
1688 "HTMLEditor::PrepareInlineStylesForCaret() failed, but ignored");
1692 firstRange = SelectionRef().GetRangeAt(0);
1693 if (NS_WARN_IF(!firstRange)) {
1694 return NS_ERROR_FAILURE;
1697 atStartOfSelection = EditorDOMPoint(firstRange->StartRef());
1698 if (NS_WARN_IF(!atStartOfSelection.IsSet())) {
1699 return NS_ERROR_FAILURE;
1701 MOZ_ASSERT(atStartOfSelection.IsSetAndValid());
1703 // Do nothing if the node is read-only
1704 if (!HTMLEditUtils::IsSimplyEditableNode(
1705 *atStartOfSelection.GetContainer())) {
1706 return NS_SUCCESS_DOM_NO_OPERATION;
1709 Result<EditorDOMPoint, nsresult> insertLineFeedResult =
1710 HandleInsertLinefeed(atStartOfSelection, *editingHost);
1711 if (MOZ_UNLIKELY(insertLineFeedResult.isErr())) {
1712 NS_WARNING("HTMLEditor::HandleInsertLinefeed() failed");
1713 return insertLineFeedResult.unwrapErr();
1715 rv = CollapseSelectionTo(insertLineFeedResult.inspect());
1716 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
1717 "EditorBase::CollapseSelectionTo() failed");
1718 return rv;
1721 Result<EditActionResult, nsresult>
1722 HTMLEditor::InsertParagraphSeparatorAsSubAction(const Element& aEditingHost) {
1723 if (NS_WARN_IF(!mInitSucceeded)) {
1724 return Err(NS_ERROR_NOT_INITIALIZED);
1728 Result<EditActionResult, nsresult> result = CanHandleHTMLEditSubAction(
1729 CheckSelectionInReplacedElement::OnlyWhenNotInSameNode);
1730 if (MOZ_UNLIKELY(result.isErr())) {
1731 NS_WARNING("HTMLEditor::CanHandleHTMLEditSubAction() failed");
1732 return result;
1734 if (result.inspect().Canceled()) {
1735 return result;
1739 // XXX This may be called by execCommand() with "insertParagraph".
1740 // In such case, naming the transaction "TypingTxnName" is odd.
1741 AutoPlaceholderBatch treatAsOneTransaction(*this, *nsGkAtoms::TypingTxnName,
1742 ScrollSelectionIntoView::Yes,
1743 __FUNCTION__);
1745 IgnoredErrorResult ignoredError;
1746 AutoEditSubActionNotifier startToHandleEditSubAction(
1747 *this, EditSubAction::eInsertParagraphSeparator, nsIEditor::eNext,
1748 ignoredError);
1749 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
1750 return Err(ignoredError.StealNSResult());
1752 NS_WARNING_ASSERTION(
1753 !ignoredError.Failed(),
1754 "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
1756 UndefineCaretBidiLevel();
1758 // If the selection isn't collapsed, delete it.
1759 if (!SelectionRef().IsCollapsed()) {
1760 nsresult rv =
1761 DeleteSelectionAsSubAction(nsIEditor::eNone, nsIEditor::eStrip);
1762 if (NS_FAILED(rv)) {
1763 NS_WARNING(
1764 "EditorBase::DeleteSelectionAsSubAction(eNone, eStrip) failed");
1765 return Err(rv);
1769 nsresult rv = EnsureNoPaddingBRElementForEmptyEditor();
1770 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
1771 return Err(NS_ERROR_EDITOR_DESTROYED);
1773 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
1774 "EditorBase::EnsureNoPaddingBRElementForEmptyEditor() "
1775 "failed, but ignored");
1777 if (NS_SUCCEEDED(rv) && SelectionRef().IsCollapsed()) {
1778 nsresult rv = EnsureCaretNotAfterInvisibleBRElement();
1779 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
1780 return Err(NS_ERROR_EDITOR_DESTROYED);
1782 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
1783 "HTMLEditor::EnsureCaretNotAfterInvisibleBRElement() "
1784 "failed, but ignored");
1785 if (NS_SUCCEEDED(rv)) {
1786 nsresult rv = PrepareInlineStylesForCaret();
1787 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
1788 return Err(NS_ERROR_EDITOR_DESTROYED);
1790 NS_WARNING_ASSERTION(
1791 NS_SUCCEEDED(rv),
1792 "HTMLEditor::PrepareInlineStylesForCaret() failed, but ignored");
1796 AutoRangeArray selectionRanges(SelectionRef());
1798 // If the editing host is the body element, the selection may be outside
1799 // aEditingHost. In the case, we should use the editing host outside the
1800 // <body> only here for keeping our traditional behavior for now.
1801 // This should be fixed in bug 1634351.
1802 const Element* editingHostMaybeOutsideBody = &aEditingHost;
1803 if (aEditingHost.IsHTMLElement(nsGkAtoms::body)) {
1804 editingHostMaybeOutsideBody = ComputeEditingHost(LimitInBodyElement::No);
1805 if (NS_WARN_IF(!editingHostMaybeOutsideBody)) {
1806 return Err(NS_ERROR_FAILURE);
1809 selectionRanges.EnsureOnlyEditableRanges(*editingHostMaybeOutsideBody);
1810 if (NS_WARN_IF(selectionRanges.Ranges().IsEmpty())) {
1811 return Err(NS_ERROR_FAILURE);
1815 auto pointToInsert =
1816 selectionRanges.GetFirstRangeStartPoint<EditorDOMPoint>();
1817 if (NS_WARN_IF(!pointToInsert.IsInContentNode())) {
1818 return Err(NS_ERROR_FAILURE);
1820 while (true) {
1821 Element* element = pointToInsert.GetContainerOrContainerParentElement();
1822 if (MOZ_UNLIKELY(!element)) {
1823 return Err(NS_ERROR_FAILURE);
1825 // If the element can have a <br> element (it means that the element or its
1826 // container must be able to have <div> or <p> too), we can handle
1827 // insertParagraph at the point.
1828 if (HTMLEditUtils::CanNodeContain(*element, *nsGkAtoms::br)) {
1829 break;
1831 // Otherwise, try to insert paragraph at the parent.
1832 pointToInsert = pointToInsert.ParentPoint();
1835 if (IsMailEditor()) {
1836 if (RefPtr<Element> mailCiteElement = GetMostDistantAncestorMailCiteElement(
1837 *pointToInsert.ContainerAs<nsIContent>())) {
1838 // Split any mailcites in the way. Should we abort this if we encounter
1839 // table cell boundaries?
1840 Result<EditorDOMPoint, nsresult> atNewBRElementOrError =
1841 HandleInsertParagraphInMailCiteElement(*mailCiteElement,
1842 pointToInsert, aEditingHost);
1843 if (MOZ_UNLIKELY(atNewBRElementOrError.isErr())) {
1844 NS_WARNING(
1845 "HTMLEditor::HandleInsertParagraphInMailCiteElement() failed");
1846 return atNewBRElementOrError.propagateErr();
1848 EditorDOMPoint pointToPutCaret = atNewBRElementOrError.unwrap();
1849 MOZ_ASSERT(pointToPutCaret.IsSet());
1850 pointToPutCaret.SetInterlinePosition(InterlinePosition::StartOfNextLine);
1851 MOZ_ASSERT(pointToPutCaret.GetChild());
1852 MOZ_ASSERT(pointToPutCaret.GetChild()->IsHTMLElement(nsGkAtoms::br));
1853 nsresult rv = CollapseSelectionTo(pointToPutCaret);
1854 if (NS_FAILED(rv)) {
1855 NS_WARNING("EditorBase::CollapseSelectionTo() failed");
1856 return Err(rv);
1858 return EditActionResult::HandledResult();
1862 // If the active editing host is an inline element, or if the active editing
1863 // host is the block parent itself and we're configured to use <br> as a
1864 // paragraph separator, just append a <br>.
1865 // If the editing host parent element is editable, it means that the editing
1866 // host must be a <body> element and the selection may be outside the body
1867 // element. If the selection is outside the editing host, we should not
1868 // insert new paragraph nor <br> element.
1869 // XXX Currently, we don't support editing outside <body> element, but Blink
1870 // does it.
1871 if (aEditingHost.GetParentElement() &&
1872 HTMLEditUtils::IsSimplyEditableNode(*aEditingHost.GetParentElement()) &&
1873 !nsContentUtils::ContentIsFlattenedTreeDescendantOf(
1874 pointToInsert.ContainerAs<nsIContent>(), &aEditingHost)) {
1875 return Err(NS_ERROR_EDITOR_NO_EDITABLE_RANGE);
1878 auto InsertLineBreakInstead =
1879 [](const Element* aEditableBlockElement,
1880 const EditorDOMPoint& aCandidatePointToSplit,
1881 ParagraphSeparator aDefaultParagraphSeparator,
1882 const Element& aEditingHost) {
1883 // If there is no block parent in the editing host, i.e., the editing
1884 // host itself is also a non-block element, we should insert a line
1885 // break.
1886 if (!aEditableBlockElement) {
1887 // XXX Chromium checks if the CSS box of the editing host is a block.
1888 return true;
1891 // If the editable block element is not splittable, e.g., it's an
1892 // editing host, and the default paragraph separator is <br> or the
1893 // element cannot contain a <p> element, we should insert a <br>
1894 // element.
1895 if (!HTMLEditUtils::IsSplittableNode(*aEditableBlockElement)) {
1896 return aDefaultParagraphSeparator == ParagraphSeparator::br ||
1897 !HTMLEditUtils::CanElementContainParagraph(
1898 *aEditableBlockElement) ||
1899 (HTMLEditUtils::ShouldInsertLinefeedCharacter(
1900 aCandidatePointToSplit, aEditingHost) &&
1901 HTMLEditUtils::IsDisplayOutsideInline(aEditingHost));
1904 // If the nearest block parent is a single-line container declared in
1905 // the execCommand spec and not the editing host, we should separate the
1906 // block even if the default paragraph separator is <br> element.
1907 if (HTMLEditUtils::IsSingleLineContainer(*aEditableBlockElement)) {
1908 return false;
1911 // Otherwise, unless there is no block ancestor which can contain <p>
1912 // element, we shouldn't insert a line break here.
1913 for (const Element* editableBlockAncestor = aEditableBlockElement;
1914 editableBlockAncestor;
1915 editableBlockAncestor = HTMLEditUtils::GetAncestorElement(
1916 *editableBlockAncestor,
1917 HTMLEditUtils::ClosestEditableBlockElementOrButtonElement,
1918 BlockInlineCheck::UseComputedDisplayOutsideStyle)) {
1919 if (HTMLEditUtils::CanElementContainParagraph(
1920 *editableBlockAncestor)) {
1921 return false;
1924 return true;
1927 // Look for the nearest parent block. However, don't return error even if
1928 // there is no block parent here because in such case, i.e., editing host
1929 // is an inline element, we should insert <br> simply.
1930 RefPtr<Element> editableBlockElement =
1931 HTMLEditUtils::GetInclusiveAncestorElement(
1932 *pointToInsert.ContainerAs<nsIContent>(),
1933 HTMLEditUtils::ClosestEditableBlockElementOrButtonElement,
1934 BlockInlineCheck::UseComputedDisplayOutsideStyle);
1936 // If we cannot insert a <p>/<div> element at the selection, we should insert
1937 // a <br> element or a linefeed instead.
1938 const ParagraphSeparator separator = GetDefaultParagraphSeparator();
1939 if (InsertLineBreakInstead(editableBlockElement, pointToInsert, separator,
1940 aEditingHost)) {
1941 // For backward compatibility, we should not insert a linefeed if
1942 // paragraph separator is set to "br" which is Gecko-specific mode.
1943 if (separator != ParagraphSeparator::br &&
1944 HTMLEditUtils::ShouldInsertLinefeedCharacter(pointToInsert,
1945 aEditingHost)) {
1946 Result<EditorDOMPoint, nsresult> insertLineFeedResult =
1947 HandleInsertLinefeed(pointToInsert, aEditingHost);
1948 if (MOZ_UNLIKELY(insertLineFeedResult.isErr())) {
1949 NS_WARNING("HTMLEditor::HandleInsertLinefeed() failed");
1950 return insertLineFeedResult.propagateErr();
1952 nsresult rv = CollapseSelectionTo(insertLineFeedResult.inspect());
1953 if (NS_FAILED(rv)) {
1954 NS_WARNING("EditorBase::CollapseSelectionTo() failed");
1955 return Err(rv);
1957 return EditActionResult::HandledResult();
1960 Result<CreateElementResult, nsresult> insertBRElementResult =
1961 HandleInsertBRElement(pointToInsert, aEditingHost);
1962 if (MOZ_UNLIKELY(insertBRElementResult.isErr())) {
1963 NS_WARNING("HTMLEditor::HandleInsertBRElement() failed");
1964 return insertBRElementResult.propagateErr();
1966 nsresult rv =
1967 insertBRElementResult.inspect().SuggestCaretPointTo(*this, {});
1968 if (NS_FAILED(rv)) {
1969 NS_WARNING("CreateElementResult::SuggestCaretPointTo() failed");
1970 return Err(rv);
1972 return EditActionResult::HandledResult();
1975 // If somebody wants to restrict caret position in a block element below,
1976 // we should guarantee it. Otherwise, we can put caret to the candidate
1977 // point.
1978 auto CollapseSelection =
1979 [this](const EditorDOMPoint& aCandidatePointToPutCaret,
1980 const Element* aBlockElementShouldHaveCaret,
1981 const SuggestCaretOptions& aOptions)
1982 MOZ_CAN_RUN_SCRIPT -> nsresult {
1983 if (!aCandidatePointToPutCaret.IsSet()) {
1984 if (aOptions.contains(SuggestCaret::OnlyIfHasSuggestion)) {
1985 return NS_OK;
1987 return aOptions.contains(SuggestCaret::AndIgnoreTrivialError)
1988 ? NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR
1989 : NS_ERROR_FAILURE;
1991 EditorDOMPoint pointToPutCaret(aCandidatePointToPutCaret);
1992 if (aBlockElementShouldHaveCaret) {
1993 Result<EditorDOMPoint, nsresult> pointToPutCaretOrError =
1994 HTMLEditUtils::ComputePointToPutCaretInElementIfOutside<
1995 EditorDOMPoint>(*aBlockElementShouldHaveCaret,
1996 aCandidatePointToPutCaret);
1997 if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) {
1998 NS_WARNING(
1999 "HTMLEditUtils::ComputePointToPutCaretInElementIfOutside() "
2000 "failed, but ignored");
2001 } else if (pointToPutCaretOrError.inspect().IsSet()) {
2002 pointToPutCaret = pointToPutCaretOrError.unwrap();
2005 nsresult rv = CollapseSelectionTo(pointToPutCaret);
2006 if (NS_FAILED(rv) && MOZ_LIKELY(rv != NS_ERROR_EDITOR_DESTROYED) &&
2007 aOptions.contains(SuggestCaret::AndIgnoreTrivialError)) {
2008 rv = NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR;
2010 return rv;
2013 RefPtr<Element> blockElementToPutCaret;
2014 // If the default paragraph separator is not <br> and selection is not in
2015 // a splittable block element, we should wrap selected contents in a new
2016 // paragraph, then, split it.
2017 if (!HTMLEditUtils::IsSplittableNode(*editableBlockElement) &&
2018 separator != ParagraphSeparator::br) {
2019 MOZ_ASSERT(separator == ParagraphSeparator::div ||
2020 separator == ParagraphSeparator::p);
2021 // FIXME: If there is no splittable block element, the other browsers wrap
2022 // the right nodes into new paragraph, but keep the left node as-is.
2023 // We should follow them to make here simpler and better compatibility.
2024 Result<RefPtr<Element>, nsresult> suggestBlockElementToPutCaretOrError =
2025 FormatBlockContainerWithTransaction(
2026 selectionRanges,
2027 MOZ_KnownLive(HTMLEditor::ToParagraphSeparatorTagName(separator)),
2028 // For keeping the traditional behavior at insertParagraph command,
2029 // let's use the XUL paragraph state command targets even if we're
2030 // handling HTML insertParagraph command.
2031 FormatBlockMode::XULParagraphStateCommand, aEditingHost);
2032 if (MOZ_UNLIKELY(suggestBlockElementToPutCaretOrError.isErr())) {
2033 NS_WARNING("HTMLEditor::FormatBlockContainerWithTransaction() failed");
2034 return suggestBlockElementToPutCaretOrError.propagateErr();
2036 if (selectionRanges.HasSavedRanges()) {
2037 selectionRanges.RestoreFromSavedRanges();
2039 pointToInsert = selectionRanges.GetFirstRangeStartPoint<EditorDOMPoint>();
2040 if (NS_WARN_IF(!pointToInsert.IsInContentNode())) {
2041 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
2043 MOZ_ASSERT(pointToInsert.IsSetAndValid());
2044 blockElementToPutCaret = suggestBlockElementToPutCaretOrError.unwrap();
2046 editableBlockElement = HTMLEditUtils::GetInclusiveAncestorElement(
2047 *pointToInsert.ContainerAs<nsIContent>(),
2048 HTMLEditUtils::ClosestEditableBlockElementOrButtonElement,
2049 BlockInlineCheck::UseComputedDisplayOutsideStyle);
2050 if (NS_WARN_IF(!editableBlockElement)) {
2051 return Err(NS_ERROR_UNEXPECTED);
2053 if (NS_WARN_IF(!HTMLEditUtils::IsSplittableNode(*editableBlockElement))) {
2054 // Didn't create a new block for some reason, fall back to <br>
2055 Result<CreateElementResult, nsresult> insertBRElementResult =
2056 HandleInsertBRElement(pointToInsert, aEditingHost);
2057 if (MOZ_UNLIKELY(insertBRElementResult.isErr())) {
2058 NS_WARNING("HTMLEditor::HandleInsertBRElement() failed");
2059 return insertBRElementResult.propagateErr();
2061 CreateElementResult unwrappedInsertBRElementResult =
2062 insertBRElementResult.unwrap();
2063 EditorDOMPoint pointToPutCaret =
2064 unwrappedInsertBRElementResult.UnwrapCaretPoint();
2065 if (MOZ_UNLIKELY(!pointToPutCaret.IsSet())) {
2066 NS_WARNING(
2067 "HTMLEditor::HandleInsertBRElement() didn't suggest a point to put "
2068 "caret");
2069 return Err(NS_ERROR_FAILURE);
2071 nsresult rv =
2072 CollapseSelection(pointToPutCaret, blockElementToPutCaret, {});
2073 if (NS_FAILED(rv)) {
2074 NS_WARNING("CollapseSelection() failed");
2075 return Err(rv);
2077 return EditActionResult::HandledResult();
2079 // We want to collapse selection in the editable block element.
2080 blockElementToPutCaret = editableBlockElement;
2083 // If block is empty, populate with br. (For example, imagine a div that
2084 // contains the word "text". The user selects "text" and types return.
2085 // "Text" is deleted leaving an empty block. We want to put in one br to
2086 // make block have a line. Then code further below will put in a second br.)
2087 RefPtr<Element> insertedPaddingBRElement;
2088 if (HTMLEditUtils::IsEmptyBlockElement(
2089 *editableBlockElement,
2090 {EmptyCheckOption::TreatSingleBRElementAsVisible},
2091 BlockInlineCheck::UseComputedDisplayOutsideStyle)) {
2092 Result<CreateElementResult, nsresult> insertBRElementResult =
2093 InsertBRElement(WithTransaction::Yes,
2094 EditorDOMPoint::AtEndOf(*editableBlockElement));
2095 if (MOZ_UNLIKELY(insertBRElementResult.isErr())) {
2096 NS_WARNING("HTMLEditor::InsertBRElement(WithTransaction::Yes) failed");
2097 return insertBRElementResult.propagateErr();
2099 CreateElementResult unwrappedInsertBRElementResult =
2100 insertBRElementResult.unwrap();
2101 unwrappedInsertBRElementResult.IgnoreCaretPointSuggestion();
2102 MOZ_ASSERT(unwrappedInsertBRElementResult.GetNewNode());
2103 insertedPaddingBRElement = unwrappedInsertBRElementResult.UnwrapNewNode();
2105 pointToInsert = selectionRanges.GetFirstRangeStartPoint<EditorDOMPoint>();
2106 if (NS_WARN_IF(!pointToInsert.IsInContentNode())) {
2107 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
2111 RefPtr<Element> maybeNonEditableListItem =
2112 HTMLEditUtils::GetClosestAncestorListItemElement(*editableBlockElement,
2113 &aEditingHost);
2114 if (maybeNonEditableListItem &&
2115 HTMLEditUtils::IsSplittableNode(*maybeNonEditableListItem)) {
2116 Result<InsertParagraphResult, nsresult> insertParagraphInListItemResult =
2117 HandleInsertParagraphInListItemElement(*maybeNonEditableListItem,
2118 pointToInsert, aEditingHost);
2119 if (MOZ_UNLIKELY(insertParagraphInListItemResult.isErr())) {
2120 if (NS_WARN_IF(insertParagraphInListItemResult.unwrapErr() ==
2121 NS_ERROR_EDITOR_DESTROYED)) {
2122 return Err(NS_ERROR_EDITOR_DESTROYED);
2124 NS_WARNING(
2125 "HTMLEditor::HandleInsertParagraphInListItemElement() failed, but "
2126 "ignored");
2127 return EditActionResult::HandledResult();
2129 InsertParagraphResult unwrappedInsertParagraphInListItemResult =
2130 insertParagraphInListItemResult.unwrap();
2131 MOZ_ASSERT(unwrappedInsertParagraphInListItemResult.Handled());
2132 MOZ_ASSERT(unwrappedInsertParagraphInListItemResult.GetNewNode());
2133 const RefPtr<Element> listItemOrParagraphElement =
2134 unwrappedInsertParagraphInListItemResult.UnwrapNewNode();
2135 const EditorDOMPoint pointToPutCaret =
2136 unwrappedInsertParagraphInListItemResult.UnwrapCaretPoint();
2137 nsresult rv = CollapseSelection(pointToPutCaret, listItemOrParagraphElement,
2138 {SuggestCaret::AndIgnoreTrivialError});
2139 if (NS_FAILED(rv)) {
2140 NS_WARNING("CollapseSelection() failed");
2141 return Err(rv);
2143 NS_WARNING_ASSERTION(rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
2144 "CollapseSelection() failed, but ignored");
2145 return EditActionResult::HandledResult();
2148 if (HTMLEditUtils::IsHeader(*editableBlockElement)) {
2149 Result<InsertParagraphResult, nsresult>
2150 insertParagraphInHeadingElementResult =
2151 HandleInsertParagraphInHeadingElement(*editableBlockElement,
2152 pointToInsert);
2153 if (MOZ_UNLIKELY(insertParagraphInHeadingElementResult.isErr())) {
2154 NS_WARNING(
2155 "HTMLEditor::HandleInsertParagraphInHeadingElement() failed, but "
2156 "ignored");
2157 return EditActionResult::HandledResult();
2159 InsertParagraphResult unwrappedInsertParagraphInHeadingElementResult =
2160 insertParagraphInHeadingElementResult.unwrap();
2161 if (unwrappedInsertParagraphInHeadingElementResult.Handled()) {
2162 MOZ_ASSERT(unwrappedInsertParagraphInHeadingElementResult.GetNewNode());
2163 blockElementToPutCaret =
2164 unwrappedInsertParagraphInHeadingElementResult.UnwrapNewNode();
2166 const EditorDOMPoint pointToPutCaret =
2167 unwrappedInsertParagraphInHeadingElementResult.UnwrapCaretPoint();
2168 nsresult rv = CollapseSelection(pointToPutCaret, blockElementToPutCaret,
2169 {SuggestCaret::OnlyIfHasSuggestion,
2170 SuggestCaret::AndIgnoreTrivialError});
2171 if (NS_FAILED(rv)) {
2172 NS_WARNING("CollapseSelection() failed");
2173 return Err(rv);
2175 NS_WARNING_ASSERTION(rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
2176 "CollapseSelection() failed, but ignored");
2177 return EditActionResult::HandledResult();
2180 // XXX Ideally, we should take same behavior with both <p> container and
2181 // <div> container. However, we are still using <br> as default
2182 // paragraph separator (non-standard) and we've split only <p> container
2183 // long time. Therefore, some web apps may depend on this behavior like
2184 // Gmail. So, let's use traditional odd behavior only when the default
2185 // paragraph separator is <br>. Otherwise, take consistent behavior
2186 // between <p> container and <div> container.
2187 if ((separator == ParagraphSeparator::br &&
2188 editableBlockElement->IsHTMLElement(nsGkAtoms::p)) ||
2189 (separator != ParagraphSeparator::br &&
2190 editableBlockElement->IsAnyOfHTMLElements(nsGkAtoms::p,
2191 nsGkAtoms::div))) {
2192 // Paragraphs: special rules to look for <br>s
2193 Result<SplitNodeResult, nsresult> splitNodeResult =
2194 HandleInsertParagraphInParagraph(
2195 *editableBlockElement,
2196 insertedPaddingBRElement ? EditorDOMPoint(insertedPaddingBRElement)
2197 : pointToInsert,
2198 aEditingHost);
2199 if (MOZ_UNLIKELY(splitNodeResult.isErr())) {
2200 NS_WARNING("HTMLEditor::HandleInsertParagraphInParagraph() failed");
2201 return splitNodeResult.propagateErr();
2203 if (splitNodeResult.inspect().Handled()) {
2204 SplitNodeResult unwrappedSplitNodeResult = splitNodeResult.unwrap();
2205 const RefPtr<Element> rightParagraphElement =
2206 unwrappedSplitNodeResult.DidSplit()
2207 ? unwrappedSplitNodeResult.GetNextContentAs<Element>()
2208 : blockElementToPutCaret.get();
2209 const EditorDOMPoint pointToPutCaret =
2210 unwrappedSplitNodeResult.UnwrapCaretPoint();
2211 nsresult rv = CollapseSelection(pointToPutCaret, rightParagraphElement,
2212 {SuggestCaret::AndIgnoreTrivialError});
2213 if (NS_FAILED(rv)) {
2214 NS_WARNING("CollapseSelection() failed");
2215 return Err(rv);
2217 NS_WARNING_ASSERTION(rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
2218 "CollapseSelection() failed, but ignored");
2219 return EditActionResult::HandledResult();
2221 MOZ_ASSERT(!splitNodeResult.inspect().HasCaretPointSuggestion());
2223 // Fall through, if HandleInsertParagraphInParagraph() didn't handle it.
2224 MOZ_ASSERT(pointToInsert.IsSetAndValid(),
2225 "HTMLEditor::HandleInsertParagraphInParagraph() shouldn't touch "
2226 "the DOM tree if it returns not-handled state");
2229 // If nobody handles this edit action, let's insert new <br> at the selection.
2230 Result<CreateElementResult, nsresult> insertBRElementResult =
2231 HandleInsertBRElement(pointToInsert, aEditingHost);
2232 if (MOZ_UNLIKELY(insertBRElementResult.isErr())) {
2233 NS_WARNING("HTMLEditor::HandleInsertBRElement() failed");
2234 return insertBRElementResult.propagateErr();
2236 CreateElementResult unwrappedInsertBRElementResult =
2237 insertBRElementResult.unwrap();
2238 EditorDOMPoint pointToPutCaret =
2239 unwrappedInsertBRElementResult.UnwrapCaretPoint();
2240 rv = CollapseSelection(pointToPutCaret, blockElementToPutCaret, {});
2241 if (NS_FAILED(rv)) {
2242 NS_WARNING("CreateElementResult::SuggestCaretPointTo() failed");
2243 return Err(rv);
2245 return EditActionResult::HandledResult();
2248 Result<CreateElementResult, nsresult> HTMLEditor::HandleInsertBRElement(
2249 const EditorDOMPoint& aPointToBreak, const Element& aEditingHost) {
2250 MOZ_ASSERT(aPointToBreak.IsSet());
2251 MOZ_ASSERT(IsEditActionDataAvailable());
2253 const bool editingHostIsEmpty = HTMLEditUtils::IsEmptyNode(
2254 aEditingHost, {EmptyCheckOption::TreatNonEditableContentAsInvisible});
2255 WSRunScanner wsRunScanner(&aEditingHost, aPointToBreak,
2256 BlockInlineCheck::UseComputedDisplayStyle);
2257 WSScanResult backwardScanResult =
2258 wsRunScanner.ScanPreviousVisibleNodeOrBlockBoundaryFrom(aPointToBreak);
2259 if (MOZ_UNLIKELY(backwardScanResult.Failed())) {
2260 NS_WARNING(
2261 "WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundaryFrom() failed");
2262 return Err(NS_ERROR_FAILURE);
2264 const bool brElementIsAfterBlock = backwardScanResult.ReachedBlockBoundary();
2265 WSScanResult forwardScanResult =
2266 wsRunScanner.ScanNextVisibleNodeOrBlockBoundaryFrom(aPointToBreak);
2267 if (MOZ_UNLIKELY(forwardScanResult.Failed())) {
2268 NS_WARNING("WSRunScanner::ScanNextVisibleNodeOrBlockBoundaryFrom() failed");
2269 return Err(NS_ERROR_FAILURE);
2271 const bool brElementIsBeforeBlock = forwardScanResult.ReachedBlockBoundary();
2273 // First, insert a <br> element.
2274 RefPtr<Element> brElement;
2275 if (IsPlaintextMailComposer()) {
2276 Result<CreateElementResult, nsresult> insertBRElementResult =
2277 InsertBRElement(WithTransaction::Yes, aPointToBreak);
2278 if (MOZ_UNLIKELY(insertBRElementResult.isErr())) {
2279 NS_WARNING("HTMLEditor::InsertBRElement(WithTransaction::Yes) failed");
2280 return insertBRElementResult;
2282 CreateElementResult unwrappedInsertBRElementResult =
2283 insertBRElementResult.unwrap();
2284 // We'll return with suggesting new caret position and nobody refers
2285 // selection after here. So we don't need to update selection here.
2286 unwrappedInsertBRElementResult.IgnoreCaretPointSuggestion();
2287 MOZ_ASSERT(unwrappedInsertBRElementResult.GetNewNode());
2288 brElement = unwrappedInsertBRElementResult.UnwrapNewNode();
2289 } else {
2290 EditorDOMPoint pointToBreak(aPointToBreak);
2291 // If the container of the break is a link, we need to split it and
2292 // insert new <br> between the split links.
2293 RefPtr<Element> linkNode =
2294 HTMLEditor::GetLinkElement(pointToBreak.GetContainer());
2295 if (linkNode) {
2296 Result<SplitNodeResult, nsresult> splitLinkNodeResult =
2297 SplitNodeDeepWithTransaction(
2298 *linkNode, pointToBreak,
2299 SplitAtEdges::eDoNotCreateEmptyContainer);
2300 if (MOZ_UNLIKELY(splitLinkNodeResult.isErr())) {
2301 NS_WARNING(
2302 "HTMLEditor::SplitNodeDeepWithTransaction(SplitAtEdges::"
2303 "eDoNotCreateEmptyContainer) failed");
2304 return splitLinkNodeResult.propagateErr();
2306 // TODO: Some methods called by
2307 // WhiteSpaceVisibilityKeeper::InsertBRElement() use
2308 // ComputeEditingHost() which depends on selection. Therefore,
2309 // we cannot skip updating selection here.
2310 nsresult rv = splitLinkNodeResult.inspect().SuggestCaretPointTo(
2311 *this, {SuggestCaret::OnlyIfHasSuggestion,
2312 SuggestCaret::OnlyIfTransactionsAllowedToDoIt});
2313 if (NS_FAILED(rv)) {
2314 NS_WARNING("SplitNodeResult::SuggestCaretPointTo() failed");
2315 return Err(rv);
2317 pointToBreak =
2318 splitLinkNodeResult.inspect().AtSplitPoint<EditorDOMPoint>();
2320 Result<CreateElementResult, nsresult> insertBRElementResult =
2321 WhiteSpaceVisibilityKeeper::InsertBRElement(*this, pointToBreak,
2322 aEditingHost);
2323 if (MOZ_UNLIKELY(insertBRElementResult.isErr())) {
2324 NS_WARNING("WhiteSpaceVisibilityKeeper::InsertBRElement() failed");
2325 return insertBRElementResult;
2327 CreateElementResult unwrappedInsertBRElementResult =
2328 insertBRElementResult.unwrap();
2329 // We'll return with suggesting new caret position and nobody refers
2330 // selection after here. So we don't need to update selection here.
2331 unwrappedInsertBRElementResult.IgnoreCaretPointSuggestion();
2332 brElement = unwrappedInsertBRElementResult.UnwrapNewNode();
2333 MOZ_ASSERT(brElement);
2336 if (MOZ_UNLIKELY(!brElement->GetParentNode())) {
2337 NS_WARNING("Inserted <br> element was removed by the web app");
2338 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
2340 auto afterBRElement = EditorDOMPoint::After(brElement);
2342 auto InsertAdditionalInvisibleLineBreak =
2343 [&]() MOZ_CAN_RUN_SCRIPT -> Result<CreateElementResult, nsresult> {
2344 // Empty last line is invisible if it's immediately before either parent or
2345 // another block's boundary so that we need to put invisible <br> element
2346 // here for making it visible.
2347 Result<CreateElementResult, nsresult> invisibleAdditionalBRElementResult =
2348 WhiteSpaceVisibilityKeeper::InsertBRElement(*this, afterBRElement,
2349 aEditingHost);
2350 if (MOZ_UNLIKELY(invisibleAdditionalBRElementResult.isErr())) {
2351 NS_WARNING("WhiteSpaceVisibilityKeeper::InsertBRElement() failed");
2352 return invisibleAdditionalBRElementResult;
2354 // afterBRElement points after the first <br> with referring an old child.
2355 // Therefore, we need to update it with new child which is the new invisible
2356 // <br>.
2357 afterBRElement.Set(
2358 invisibleAdditionalBRElementResult.inspect().GetNewNode());
2359 return invisibleAdditionalBRElementResult;
2362 if (brElementIsAfterBlock && brElementIsBeforeBlock) {
2363 // We just placed a <br> between block boundaries. This is the one case
2364 // where we want the selection to be before the br we just placed, as the
2365 // br will be on a new line, rather than at end of prior line.
2366 // XXX brElementIsAfterBlock and brElementIsBeforeBlock were set before
2367 // modifying the DOM tree. So, now, the <br> element may not be
2368 // between blocks.
2369 EditorDOMPoint pointToPutCaret;
2370 if (editingHostIsEmpty) {
2371 Result<CreateElementResult, nsresult> invisibleAdditionalBRElementResult =
2372 InsertAdditionalInvisibleLineBreak();
2373 if (invisibleAdditionalBRElementResult.isErr()) {
2374 return invisibleAdditionalBRElementResult;
2376 invisibleAdditionalBRElementResult.unwrap().IgnoreCaretPointSuggestion();
2377 pointToPutCaret = std::move(afterBRElement);
2378 } else {
2379 pointToPutCaret =
2380 EditorDOMPoint(brElement, InterlinePosition::StartOfNextLine);
2382 return CreateElementResult(std::move(brElement),
2383 std::move(pointToPutCaret));
2386 WSScanResult forwardScanFromAfterBRElementResult =
2387 WSRunScanner::ScanNextVisibleNodeOrBlockBoundary(
2388 &aEditingHost, afterBRElement,
2389 BlockInlineCheck::UseComputedDisplayStyle);
2390 if (MOZ_UNLIKELY(forwardScanFromAfterBRElementResult.Failed())) {
2391 NS_WARNING("WSRunScanner::ScanNextVisibleNodeOrBlockBoundary() failed");
2392 return Err(NS_ERROR_FAILURE);
2394 if (forwardScanFromAfterBRElementResult.ReachedBRElement()) {
2395 // The next thing after the break we inserted is another break. Move the
2396 // second break to be the first break's sibling. This will prevent them
2397 // from being in different inline nodes, which would break
2398 // SetInterlinePosition(). It will also assure that if the user clicks
2399 // away and then clicks back on their new blank line, they will still get
2400 // the style from the line above.
2401 if (brElement->GetNextSibling() !=
2402 forwardScanFromAfterBRElementResult.BRElementPtr()) {
2403 MOZ_ASSERT(forwardScanFromAfterBRElementResult.BRElementPtr());
2404 Result<MoveNodeResult, nsresult> moveBRElementResult =
2405 MoveNodeWithTransaction(
2406 MOZ_KnownLive(
2407 *forwardScanFromAfterBRElementResult.BRElementPtr()),
2408 afterBRElement);
2409 if (MOZ_UNLIKELY(moveBRElementResult.isErr())) {
2410 NS_WARNING("HTMLEditor::MoveNodeWithTransaction() failed");
2411 return moveBRElementResult.propagateErr();
2413 nsresult rv = moveBRElementResult.inspect().SuggestCaretPointTo(
2414 *this, {SuggestCaret::OnlyIfHasSuggestion,
2415 SuggestCaret::OnlyIfTransactionsAllowedToDoIt,
2416 SuggestCaret::AndIgnoreTrivialError});
2417 if (NS_FAILED(rv)) {
2418 NS_WARNING("MoveNodeResult::SuggestCaretPointTo() failed");
2419 return Err(rv);
2421 NS_WARNING_ASSERTION(
2422 rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
2423 "MoveNodeResult::SuggestCaretPointTo() failed, but ignored");
2425 } else if (forwardScanFromAfterBRElementResult.ReachedBlockBoundary() &&
2426 !brElementIsAfterBlock) {
2427 Result<CreateElementResult, nsresult> invisibleAdditionalBRElementResult =
2428 InsertAdditionalInvisibleLineBreak();
2429 if (invisibleAdditionalBRElementResult.isErr()) {
2430 return invisibleAdditionalBRElementResult;
2432 invisibleAdditionalBRElementResult.unwrap().IgnoreCaretPointSuggestion();
2435 // We want the caret to stick to whatever is past the break. This is because
2436 // the break is on the same line we were on, but the next content will be on
2437 // the following line.
2439 // An exception to this is if the break has a next sibling that is a block
2440 // node. Then we stick to the left to avoid an uber caret.
2441 nsIContent* nextSiblingOfBRElement = brElement->GetNextSibling();
2442 afterBRElement.SetInterlinePosition(
2443 nextSiblingOfBRElement && HTMLEditUtils::IsBlockElement(
2444 *nextSiblingOfBRElement,
2445 BlockInlineCheck::UseComputedDisplayStyle)
2446 ? InterlinePosition::EndOfLine
2447 : InterlinePosition::StartOfNextLine);
2448 return CreateElementResult(std::move(brElement), afterBRElement);
2451 Result<EditorDOMPoint, nsresult> HTMLEditor::HandleInsertLinefeed(
2452 const EditorDOMPoint& aPointToBreak, const Element& aEditingHost) {
2453 MOZ_ASSERT(IsEditActionDataAvailable());
2455 if (NS_WARN_IF(!aPointToBreak.IsSet())) {
2456 return Err(NS_ERROR_INVALID_ARG);
2459 const RefPtr<Document> document = GetDocument();
2460 MOZ_DIAGNOSTIC_ASSERT(document);
2461 if (NS_WARN_IF(!document)) {
2462 return Err(NS_ERROR_FAILURE);
2465 // TODO: The following code is duplicated from `HandleInsertText`. They
2466 // should be merged when we fix bug 92921.
2468 Result<EditorDOMPoint, nsresult> setStyleResult =
2469 CreateStyleForInsertText(aPointToBreak, aEditingHost);
2470 if (MOZ_UNLIKELY(setStyleResult.isErr())) {
2471 NS_WARNING("HTMLEditor::CreateStyleForInsertText() failed");
2472 return setStyleResult.propagateErr();
2475 EditorDOMPoint pointToInsert = setStyleResult.inspect().IsSet()
2476 ? setStyleResult.inspect()
2477 : aPointToBreak;
2478 if (NS_WARN_IF(!pointToInsert.IsSetAndValid()) ||
2479 NS_WARN_IF(!pointToInsert.IsInContentNode())) {
2480 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
2482 MOZ_ASSERT(pointToInsert.IsSetAndValid());
2484 // The node may not be able to have a text node so that we need to check it
2485 // here.
2486 if (!pointToInsert.IsInTextNode() &&
2487 !HTMLEditUtils::CanNodeContain(*pointToInsert.ContainerAs<nsIContent>(),
2488 *nsGkAtoms::textTagName)) {
2489 NS_WARNING(
2490 "HTMLEditor::HandleInsertLinefeed() couldn't insert a linefeed because "
2491 "the insertion position couldn't have text nodes");
2492 return Err(NS_ERROR_EDITOR_NO_EDITABLE_RANGE);
2495 AutoRestore<bool> disableListener(
2496 EditSubActionDataRef().mAdjustChangedRangeFromListener);
2497 EditSubActionDataRef().mAdjustChangedRangeFromListener = false;
2499 // TODO: We don't need AutoTransactionsConserveSelection here in the normal
2500 // cases, but removing this may cause the behavior with the legacy
2501 // mutation event listeners. We should try to delete this in a bug.
2502 AutoTransactionsConserveSelection dontChangeMySelection(*this);
2504 EditorDOMPoint pointToPutCaret;
2506 AutoTrackDOMPoint trackingInsertingPosition(RangeUpdaterRef(),
2507 &pointToInsert);
2508 Result<InsertTextResult, nsresult> insertTextResult =
2509 InsertTextWithTransaction(*document, u"\n"_ns, pointToInsert);
2510 if (MOZ_UNLIKELY(insertTextResult.isErr())) {
2511 NS_WARNING("HTMLEditor::InsertTextWithTransaction() failed");
2512 return insertTextResult.propagateErr();
2514 // Ignore the caret suggestion because of `dontChangeMySelection` above.
2515 insertTextResult.inspect().IgnoreCaretPointSuggestion();
2516 pointToPutCaret = insertTextResult.inspect().Handled()
2517 ? insertTextResult.unwrap()
2518 .EndOfInsertedTextRef()
2519 .To<EditorDOMPoint>()
2520 : pointToInsert;
2523 // Insert a padding <br> element at the end of the block element if there is
2524 // no content between the inserted linefeed and the following block boundary
2525 // to make sure that the last line is visible.
2526 // XXX Blink/WebKit inserts another linefeed character in this case. However,
2527 // for doing it, we need more work, e.g., updating serializer, deleting
2528 // unnecessary padding <br> element at modifying the last line.
2529 if (pointToPutCaret.IsInContentNode() && pointToPutCaret.IsEndOfContainer()) {
2530 WSRunScanner wsScannerAtCaret(&aEditingHost, pointToPutCaret,
2531 BlockInlineCheck::UseComputedDisplayStyle);
2532 if (wsScannerAtCaret.StartsFromPreformattedLineBreak() &&
2533 wsScannerAtCaret.EndsByBlockBoundary() &&
2534 HTMLEditUtils::CanNodeContain(*wsScannerAtCaret.GetEndReasonContent(),
2535 *nsGkAtoms::br)) {
2536 AutoTrackDOMPoint trackingInsertedPosition(RangeUpdaterRef(),
2537 &pointToInsert);
2538 AutoTrackDOMPoint trackingNewCaretPosition(RangeUpdaterRef(),
2539 &pointToPutCaret);
2540 Result<CreateElementResult, nsresult> insertBRElementResult =
2541 InsertBRElement(WithTransaction::Yes, pointToPutCaret);
2542 if (MOZ_UNLIKELY(insertBRElementResult.isErr())) {
2543 NS_WARNING("HTMLEditor::InsertBRElement(WithTransaction::Yes) failed");
2544 return insertBRElementResult.propagateErr();
2546 // We're tracking next caret position with newCaretPosition. Therefore,
2547 // we don't need to update selection here.
2548 insertBRElementResult.inspect().IgnoreCaretPointSuggestion();
2549 MOZ_ASSERT(insertBRElementResult.inspect().GetNewNode());
2553 // manually update the doc changed range so that
2554 // OnEndHandlingTopLevelEditSubActionInternal will clean up the correct
2555 // portion of the document.
2556 MOZ_ASSERT(pointToPutCaret.IsSet());
2557 if (NS_WARN_IF(!pointToPutCaret.IsSet())) {
2558 // XXX Here is odd. We did mChangedRange->SetStartAndEnd(pointToInsert,
2559 // pointToPutCaret), but it always fails because of the latter is unset.
2560 // Therefore, always returning NS_ERROR_FAILURE from here is the
2561 // traditional behavior...
2562 // TODO: Stop updating the interline position of Selection with fixing here
2563 // and returning expected point.
2564 DebugOnly<nsresult> rvIgnored =
2565 SelectionRef().SetInterlinePosition(InterlinePosition::EndOfLine);
2566 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
2567 "Selection::SetInterlinePosition(InterlinePosition::"
2568 "EndOfLine) failed, but ignored");
2569 if (NS_FAILED(TopLevelEditSubActionDataRef().mChangedRange->CollapseTo(
2570 pointToInsert))) {
2571 NS_WARNING("nsRange::CollapseTo() failed");
2572 return Err(NS_ERROR_FAILURE);
2574 NS_WARNING(
2575 "We always return NS_ERROR_FAILURE here because of a failure of "
2576 "updating mChangedRange");
2577 return Err(NS_ERROR_FAILURE);
2580 if (NS_FAILED(TopLevelEditSubActionDataRef().mChangedRange->SetStartAndEnd(
2581 pointToInsert.ToRawRangeBoundary(),
2582 pointToPutCaret.ToRawRangeBoundary()))) {
2583 NS_WARNING("nsRange::SetStartAndEnd() failed");
2584 return Err(NS_ERROR_FAILURE);
2587 pointToPutCaret.SetInterlinePosition(InterlinePosition::EndOfLine);
2588 return pointToPutCaret;
2591 Result<EditorDOMPoint, nsresult>
2592 HTMLEditor::HandleInsertParagraphInMailCiteElement(
2593 Element& aMailCiteElement, const EditorDOMPoint& aPointToSplit,
2594 const Element& aEditingHost) {
2595 MOZ_ASSERT(IsEditActionDataAvailable());
2596 MOZ_ASSERT(aPointToSplit.IsSet());
2597 NS_ASSERTION(!HTMLEditUtils::IsEmptyNode(
2598 aMailCiteElement,
2599 {EmptyCheckOption::TreatNonEditableContentAsInvisible}),
2600 "The mail-cite element will be deleted, does it expected result "
2601 "for you?");
2603 auto splitCiteElementResult =
2604 [&]() MOZ_CAN_RUN_SCRIPT -> Result<SplitNodeResult, nsresult> {
2605 EditorDOMPoint pointToSplit(aPointToSplit);
2607 // If our selection is just before a break, nudge it to be just after
2608 // it. This does two things for us. It saves us the trouble of having
2609 // to add a break here ourselves to preserve the "blockness" of the
2610 // inline span mailquote (in the inline case), and : it means the break
2611 // won't end up making an empty line that happens to be inside a
2612 // mailquote (in either inline or block case). The latter can confuse a
2613 // user if they click there and start typing, because being in the
2614 // mailquote may affect wrapping behavior, or font color, etc.
2615 WSScanResult forwardScanFromPointToSplitResult =
2616 WSRunScanner::ScanNextVisibleNodeOrBlockBoundary(
2617 &aEditingHost, pointToSplit, BlockInlineCheck::UseHTMLDefaultStyle);
2618 if (forwardScanFromPointToSplitResult.Failed()) {
2619 return Err(NS_ERROR_FAILURE);
2621 // If selection start point is before a break and it's inside the
2622 // mailquote, let's split it after the visible node.
2623 if (forwardScanFromPointToSplitResult.ReachedBRElement() &&
2624 forwardScanFromPointToSplitResult.BRElementPtr() != &aMailCiteElement &&
2625 aMailCiteElement.Contains(
2626 forwardScanFromPointToSplitResult.BRElementPtr())) {
2627 pointToSplit =
2628 forwardScanFromPointToSplitResult.PointAfterContent<EditorDOMPoint>();
2631 if (NS_WARN_IF(!pointToSplit.IsInContentNode())) {
2632 return Err(NS_ERROR_FAILURE);
2635 Result<SplitNodeResult, nsresult> splitResult =
2636 SplitNodeDeepWithTransaction(aMailCiteElement, pointToSplit,
2637 SplitAtEdges::eDoNotCreateEmptyContainer);
2638 if (MOZ_UNLIKELY(splitResult.isErr())) {
2639 NS_WARNING(
2640 "HTMLEditor::SplitNodeDeepWithTransaction(aMailCiteElement, "
2641 "SplitAtEdges::eDoNotCreateEmptyContainer) failed");
2642 return splitResult;
2644 nsresult rv = splitResult.inspect().SuggestCaretPointTo(
2645 *this, {SuggestCaret::OnlyIfHasSuggestion,
2646 SuggestCaret::OnlyIfTransactionsAllowedToDoIt});
2647 if (NS_FAILED(rv)) {
2648 NS_WARNING("SplitNodeResult::SuggestCaretPointTo() failed");
2649 return Err(rv);
2651 return splitResult;
2652 }();
2653 if (MOZ_UNLIKELY(splitCiteElementResult.isErr())) {
2654 NS_WARNING("Failed to split a mail-cite element");
2655 return splitCiteElementResult.propagateErr();
2657 SplitNodeResult unwrappedSplitCiteElementResult =
2658 splitCiteElementResult.unwrap();
2659 // When adding caret suggestion to SplitNodeResult, here didn't change
2660 // selection so that just ignore it.
2661 unwrappedSplitCiteElementResult.IgnoreCaretPointSuggestion();
2663 // Add an invisible <br> to the end of left cite node if it was a <span> of
2664 // style="display: block". This is important, since when serializing the cite
2665 // to plain text, the span which caused the visual break is discarded. So the
2666 // added <br> will guarantee that the serializer will insert a break where the
2667 // user saw one.
2668 // FYI: unwrappedSplitCiteElementResult grabs the previous node and the next
2669 // node with nsCOMPtr or EditorDOMPoint. So, it's safe to access
2670 // leftCiteElement and rightCiteElement even after changing the DOM tree
2671 // and/or selection even though it's raw pointer.
2672 auto* const leftCiteElement =
2673 unwrappedSplitCiteElementResult.GetPreviousContentAs<Element>();
2674 auto* const rightCiteElement =
2675 unwrappedSplitCiteElementResult.GetNextContentAs<Element>();
2676 if (leftCiteElement && leftCiteElement->IsHTMLElement(nsGkAtoms::span) &&
2677 // XXX Oh, this depends on layout information of new element, and it's
2678 // created by the hacky flush in DoSplitNode(). So we need to
2679 // redesign around this for bug 1710784.
2680 leftCiteElement->GetPrimaryFrame() &&
2681 leftCiteElement->GetPrimaryFrame()->IsBlockFrameOrSubclass()) {
2682 nsIContent* lastChild = leftCiteElement->GetLastChild();
2683 if (lastChild && !lastChild->IsHTMLElement(nsGkAtoms::br)) {
2684 Result<CreateElementResult, nsresult> insertInvisibleBRElementResult =
2685 InsertBRElement(WithTransaction::Yes,
2686 EditorDOMPoint::AtEndOf(*leftCiteElement));
2687 if (MOZ_UNLIKELY(insertInvisibleBRElementResult.isErr())) {
2688 NS_WARNING("HTMLEditor::InsertBRElement(WithTransaction::Yes) failed");
2689 return insertInvisibleBRElementResult.propagateErr();
2691 // We don't need to update selection here because we'll do another
2692 // InsertBRElement call soon.
2693 insertInvisibleBRElementResult.inspect().IgnoreCaretPointSuggestion();
2694 MOZ_ASSERT(insertInvisibleBRElementResult.inspect().GetNewNode());
2698 // In most cases, <br> should be inserted after current cite. However, if
2699 // left cite hasn't been created because the split point was start of the
2700 // cite node, <br> should be inserted before the current cite.
2701 Result<CreateElementResult, nsresult> insertBRElementResult = InsertBRElement(
2702 WithTransaction::Yes,
2703 unwrappedSplitCiteElementResult.AtSplitPoint<EditorDOMPoint>());
2704 if (MOZ_UNLIKELY(insertBRElementResult.isErr())) {
2705 NS_WARNING("HTMLEditor::InsertBRElement(WithTransaction::Yes) failed");
2706 return Err(insertBRElementResult.unwrapErr());
2708 CreateElementResult unwrappedInsertBRElementResult =
2709 insertBRElementResult.unwrap();
2710 // We'll return with suggesting caret position. Therefore, we don't need
2711 // to update selection here.
2712 unwrappedInsertBRElementResult.IgnoreCaretPointSuggestion();
2713 MOZ_ASSERT(unwrappedInsertBRElementResult.GetNewNode());
2715 // if aMailCiteElement wasn't a block, we might also want another break before
2716 // it. We need to examine the content both before the br we just added and
2717 // also just after it. If we don't have another br or block boundary
2718 // adjacent, then we will need a 2nd br added to achieve blank line that user
2719 // expects.
2720 if (HTMLEditUtils::IsInlineContent(
2721 aMailCiteElement, BlockInlineCheck::UseComputedDisplayStyle)) {
2722 nsresult rvOfInsertingBRElement = [&]() MOZ_CAN_RUN_SCRIPT {
2723 EditorDOMPoint pointToCreateNewBRElement(
2724 unwrappedInsertBRElementResult.GetNewNode());
2726 // XXX Cannot we replace this complicated check with just a call of
2727 // HTMLEditUtils::IsVisibleBRElement with
2728 // resultOfInsertingBRElement.inspect()?
2729 WSScanResult backwardScanFromPointToCreateNewBRElementResult =
2730 WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary(
2731 &aEditingHost, pointToCreateNewBRElement,
2732 BlockInlineCheck::UseComputedDisplayStyle);
2733 if (MOZ_UNLIKELY(
2734 backwardScanFromPointToCreateNewBRElementResult.Failed())) {
2735 NS_WARNING(
2736 "WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary() "
2737 "failed");
2738 return NS_ERROR_FAILURE;
2740 if (!backwardScanFromPointToCreateNewBRElementResult
2741 .InVisibleOrCollapsibleCharacters() &&
2742 !backwardScanFromPointToCreateNewBRElementResult
2743 .ReachedSpecialContent()) {
2744 return NS_SUCCESS_DOM_NO_OPERATION;
2746 WSScanResult forwardScanFromPointAfterNewBRElementResult =
2747 WSRunScanner::ScanNextVisibleNodeOrBlockBoundary(
2748 &aEditingHost,
2749 EditorRawDOMPoint::After(pointToCreateNewBRElement),
2750 BlockInlineCheck::UseComputedDisplayStyle);
2751 if (MOZ_UNLIKELY(forwardScanFromPointAfterNewBRElementResult.Failed())) {
2752 NS_WARNING("WSRunScanner::ScanNextVisibleNodeOrBlockBoundary() failed");
2753 return NS_ERROR_FAILURE;
2755 if (!forwardScanFromPointAfterNewBRElementResult
2756 .InVisibleOrCollapsibleCharacters() &&
2757 !forwardScanFromPointAfterNewBRElementResult
2758 .ReachedSpecialContent() &&
2759 // In case we're at the very end.
2760 !forwardScanFromPointAfterNewBRElementResult
2761 .ReachedCurrentBlockBoundary()) {
2762 return NS_SUCCESS_DOM_NO_OPERATION;
2764 Result<CreateElementResult, nsresult> insertBRElementResult =
2765 InsertBRElement(WithTransaction::Yes, pointToCreateNewBRElement);
2766 if (MOZ_UNLIKELY(insertBRElementResult.isErr())) {
2767 NS_WARNING("HTMLEditor::InsertBRElement(WithTransaction::Yes) failed");
2768 return insertBRElementResult.unwrapErr();
2770 insertBRElementResult.inspect().IgnoreCaretPointSuggestion();
2771 MOZ_ASSERT(insertBRElementResult.inspect().GetNewNode());
2772 return NS_OK;
2773 }();
2775 if (NS_FAILED(rvOfInsertingBRElement)) {
2776 NS_WARNING(
2777 "Failed to insert additional <br> element before the inline right "
2778 "mail-cite element");
2779 return Err(rvOfInsertingBRElement);
2783 if (leftCiteElement &&
2784 HTMLEditUtils::IsEmptyNode(
2785 *leftCiteElement,
2786 {EmptyCheckOption::TreatNonEditableContentAsInvisible})) {
2787 // MOZ_KnownLive(leftCiteElement) because it's grabbed by
2788 // unwrappedSplitCiteElementResult.
2789 nsresult rv = DeleteNodeWithTransaction(MOZ_KnownLive(*leftCiteElement));
2790 if (NS_FAILED(rv)) {
2791 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
2792 return Err(rv);
2796 if (rightCiteElement &&
2797 HTMLEditUtils::IsEmptyNode(
2798 *rightCiteElement,
2799 {EmptyCheckOption::TreatNonEditableContentAsInvisible})) {
2800 // MOZ_KnownLive(rightCiteElement) because it's grabbed by
2801 // unwrappedSplitCiteElementResult.
2802 nsresult rv = DeleteNodeWithTransaction(MOZ_KnownLive(*rightCiteElement));
2803 if (NS_FAILED(rv)) {
2804 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
2805 return Err(rv);
2809 if (MOZ_UNLIKELY(!unwrappedInsertBRElementResult.GetNewNode()->GetParent())) {
2810 NS_WARNING("Inserted <br> shouldn't become an orphan node");
2811 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
2813 return EditorDOMPoint(unwrappedInsertBRElementResult.GetNewNode());
2816 HTMLEditor::CharPointData
2817 HTMLEditor::GetPreviousCharPointDataForNormalizingWhiteSpaces(
2818 const EditorDOMPointInText& aPoint) const {
2819 MOZ_ASSERT(aPoint.IsSetAndValid());
2821 if (!aPoint.IsStartOfContainer()) {
2822 return CharPointData::InSameTextNode(
2823 HTMLEditor::GetPreviousCharPointType(aPoint));
2825 const auto previousCharPoint =
2826 WSRunScanner::GetPreviousEditableCharPoint<EditorRawDOMPointInText>(
2827 ComputeEditingHost(), aPoint,
2828 BlockInlineCheck::UseComputedDisplayStyle);
2829 if (!previousCharPoint.IsSet()) {
2830 return CharPointData::InDifferentTextNode(CharPointType::TextEnd);
2832 return CharPointData::InDifferentTextNode(
2833 HTMLEditor::GetCharPointType(previousCharPoint));
2836 HTMLEditor::CharPointData
2837 HTMLEditor::GetInclusiveNextCharPointDataForNormalizingWhiteSpaces(
2838 const EditorDOMPointInText& aPoint) const {
2839 MOZ_ASSERT(aPoint.IsSetAndValid());
2841 if (!aPoint.IsEndOfContainer()) {
2842 return CharPointData::InSameTextNode(HTMLEditor::GetCharPointType(aPoint));
2844 const auto nextCharPoint =
2845 WSRunScanner::GetInclusiveNextEditableCharPoint<EditorRawDOMPointInText>(
2846 ComputeEditingHost(), aPoint,
2847 BlockInlineCheck::UseComputedDisplayStyle);
2848 if (!nextCharPoint.IsSet()) {
2849 return CharPointData::InDifferentTextNode(CharPointType::TextEnd);
2851 return CharPointData::InDifferentTextNode(
2852 HTMLEditor::GetCharPointType(nextCharPoint));
2855 // static
2856 void HTMLEditor::GenerateWhiteSpaceSequence(
2857 nsAString& aResult, uint32_t aLength,
2858 const CharPointData& aPreviousCharPointData,
2859 const CharPointData& aNextCharPointData) {
2860 MOZ_ASSERT(aResult.IsEmpty());
2861 MOZ_ASSERT(aLength);
2862 // For now, this method does not assume that result will be append to
2863 // white-space sequence in the text node.
2864 MOZ_ASSERT(aPreviousCharPointData.AcrossTextNodeBoundary() ||
2865 !aPreviousCharPointData.IsCollapsibleWhiteSpace());
2866 // For now, this method does not assume that the result will be inserted
2867 // into white-space sequence nor start of white-space sequence.
2868 MOZ_ASSERT(aNextCharPointData.AcrossTextNodeBoundary() ||
2869 !aNextCharPointData.IsCollapsibleWhiteSpace());
2871 if (aLength == 1) {
2872 // Even if previous/next char is in different text node, we should put
2873 // an ASCII white-space between visible characters.
2874 // XXX This means that this does not allow to put an NBSP in HTML editor
2875 // without preformatted style. However, Chrome has same issue too.
2876 if (aPreviousCharPointData.Type() == CharPointType::VisibleChar &&
2877 aNextCharPointData.Type() == CharPointType::VisibleChar) {
2878 aResult.Assign(HTMLEditUtils::kSpace);
2879 return;
2881 // If it's start or end of text, put an NBSP.
2882 if (aPreviousCharPointData.Type() == CharPointType::TextEnd ||
2883 aNextCharPointData.Type() == CharPointType::TextEnd) {
2884 aResult.Assign(HTMLEditUtils::kNBSP);
2885 return;
2887 // If the character is next to a preformatted linefeed, we need to put
2888 // an NBSP for avoiding collapsed into the linefeed.
2889 if (aPreviousCharPointData.Type() == CharPointType::PreformattedLineBreak ||
2890 aNextCharPointData.Type() == CharPointType::PreformattedLineBreak) {
2891 aResult.Assign(HTMLEditUtils::kNBSP);
2892 return;
2894 // Now, the white-space will be inserted to a white-space sequence, but not
2895 // end of text. We can put an ASCII white-space only when both sides are
2896 // not ASCII white-spaces.
2897 aResult.Assign(
2898 aPreviousCharPointData.Type() == CharPointType::ASCIIWhiteSpace ||
2899 aNextCharPointData.Type() == CharPointType::ASCIIWhiteSpace
2900 ? HTMLEditUtils::kNBSP
2901 : HTMLEditUtils::kSpace);
2902 return;
2905 // Generate pairs of NBSP and ASCII white-space.
2906 aResult.SetLength(aLength);
2907 bool appendNBSP = true; // Basically, starts with an NBSP.
2908 char16_t* lastChar = aResult.EndWriting() - 1;
2909 for (char16_t* iter = aResult.BeginWriting(); iter != lastChar; iter++) {
2910 *iter = appendNBSP ? HTMLEditUtils::kNBSP : HTMLEditUtils::kSpace;
2911 appendNBSP = !appendNBSP;
2914 // If the final one is expected to an NBSP, we can put an NBSP simply.
2915 if (appendNBSP) {
2916 *lastChar = HTMLEditUtils::kNBSP;
2917 return;
2920 // If next char point is end of text node, an ASCII white-space or
2921 // preformatted linefeed, we need to put an NBSP.
2922 *lastChar =
2923 aNextCharPointData.AcrossTextNodeBoundary() ||
2924 aNextCharPointData.Type() == CharPointType::ASCIIWhiteSpace ||
2925 aNextCharPointData.Type() == CharPointType::PreformattedLineBreak
2926 ? HTMLEditUtils::kNBSP
2927 : HTMLEditUtils::kSpace;
2930 void HTMLEditor::ExtendRangeToDeleteWithNormalizingWhiteSpaces(
2931 EditorDOMPointInText& aStartToDelete, EditorDOMPointInText& aEndToDelete,
2932 nsAString& aNormalizedWhiteSpacesInStartNode,
2933 nsAString& aNormalizedWhiteSpacesInEndNode) const {
2934 MOZ_ASSERT(aStartToDelete.IsSetAndValid());
2935 MOZ_ASSERT(aEndToDelete.IsSetAndValid());
2936 MOZ_ASSERT(aStartToDelete.EqualsOrIsBefore(aEndToDelete));
2937 MOZ_ASSERT(aNormalizedWhiteSpacesInStartNode.IsEmpty());
2938 MOZ_ASSERT(aNormalizedWhiteSpacesInEndNode.IsEmpty());
2940 // First, check whether there is surrounding white-spaces or not, and if there
2941 // are, check whether they are collapsible or not. Note that we shouldn't
2942 // touch white-spaces in different text nodes for performance, but we need
2943 // adjacent text node's first or last character information in some cases.
2944 Element* editingHost = ComputeEditingHost();
2945 const EditorDOMPointInText precedingCharPoint =
2946 WSRunScanner::GetPreviousEditableCharPoint(
2947 editingHost, aStartToDelete,
2948 BlockInlineCheck::UseComputedDisplayStyle);
2949 const EditorDOMPointInText followingCharPoint =
2950 WSRunScanner::GetInclusiveNextEditableCharPoint(
2951 editingHost, aEndToDelete, BlockInlineCheck::UseComputedDisplayStyle);
2952 // Blink-compat: Normalize white-spaces in first node only when not removing
2953 // its last character or no text nodes follow the first node.
2954 // If removing last character of first node and there are
2955 // following text nodes, white-spaces in following text node are
2956 // normalized instead.
2957 const bool removingLastCharOfStartNode =
2958 aStartToDelete.ContainerAs<Text>() != aEndToDelete.ContainerAs<Text>() ||
2959 (aEndToDelete.IsEndOfContainer() && followingCharPoint.IsSet());
2960 const bool maybeNormalizePrecedingWhiteSpaces =
2961 !removingLastCharOfStartNode && precedingCharPoint.IsSet() &&
2962 !precedingCharPoint.IsEndOfContainer() &&
2963 precedingCharPoint.ContainerAs<Text>() ==
2964 aStartToDelete.ContainerAs<Text>() &&
2965 precedingCharPoint.IsCharCollapsibleASCIISpaceOrNBSP();
2966 const bool maybeNormalizeFollowingWhiteSpaces =
2967 followingCharPoint.IsSet() && !followingCharPoint.IsEndOfContainer() &&
2968 (followingCharPoint.ContainerAs<Text>() ==
2969 aEndToDelete.ContainerAs<Text>() ||
2970 removingLastCharOfStartNode) &&
2971 followingCharPoint.IsCharCollapsibleASCIISpaceOrNBSP();
2973 if (!maybeNormalizePrecedingWhiteSpaces &&
2974 !maybeNormalizeFollowingWhiteSpaces) {
2975 return; // There are no white-spaces.
2978 // Next, consider the range to normalize.
2979 EditorDOMPointInText startToNormalize, endToNormalize;
2980 if (maybeNormalizePrecedingWhiteSpaces) {
2981 Maybe<uint32_t> previousCharOffsetOfWhiteSpaces =
2982 HTMLEditUtils::GetPreviousNonCollapsibleCharOffset(
2983 precedingCharPoint, {WalkTextOption::TreatNBSPsCollapsible});
2984 startToNormalize.Set(precedingCharPoint.ContainerAs<Text>(),
2985 previousCharOffsetOfWhiteSpaces.isSome()
2986 ? previousCharOffsetOfWhiteSpaces.value() + 1
2987 : 0);
2988 MOZ_ASSERT(!startToNormalize.IsEndOfContainer());
2990 if (maybeNormalizeFollowingWhiteSpaces) {
2991 Maybe<uint32_t> nextCharOffsetOfWhiteSpaces =
2992 HTMLEditUtils::GetInclusiveNextNonCollapsibleCharOffset(
2993 followingCharPoint, {WalkTextOption::TreatNBSPsCollapsible});
2994 if (nextCharOffsetOfWhiteSpaces.isSome()) {
2995 endToNormalize.Set(followingCharPoint.ContainerAs<Text>(),
2996 nextCharOffsetOfWhiteSpaces.value());
2997 } else {
2998 endToNormalize.SetToEndOf(followingCharPoint.ContainerAs<Text>());
3000 MOZ_ASSERT(!endToNormalize.IsStartOfContainer());
3003 // Next, retrieve surrounding information of white-space sequence.
3004 // If we're removing first text node's last character, we need to
3005 // normalize white-spaces starts from another text node. In this case,
3006 // we need to lie for avoiding assertion in GenerateWhiteSpaceSequence().
3007 CharPointData previousCharPointData =
3008 removingLastCharOfStartNode
3009 ? CharPointData::InDifferentTextNode(CharPointType::TextEnd)
3010 : GetPreviousCharPointDataForNormalizingWhiteSpaces(
3011 startToNormalize.IsSet() ? startToNormalize : aStartToDelete);
3012 CharPointData nextCharPointData =
3013 GetInclusiveNextCharPointDataForNormalizingWhiteSpaces(
3014 endToNormalize.IsSet() ? endToNormalize : aEndToDelete);
3016 // Next, compute number of white-spaces in start/end node.
3017 uint32_t lengthInStartNode = 0, lengthInEndNode = 0;
3018 if (startToNormalize.IsSet()) {
3019 MOZ_ASSERT(startToNormalize.ContainerAs<Text>() ==
3020 aStartToDelete.ContainerAs<Text>());
3021 lengthInStartNode = aStartToDelete.Offset() - startToNormalize.Offset();
3022 MOZ_ASSERT(lengthInStartNode);
3024 if (endToNormalize.IsSet()) {
3025 lengthInEndNode =
3026 endToNormalize.ContainerAs<Text>() == aEndToDelete.ContainerAs<Text>()
3027 ? endToNormalize.Offset() - aEndToDelete.Offset()
3028 : endToNormalize.Offset();
3029 MOZ_ASSERT(lengthInEndNode);
3030 // If we normalize white-spaces in a text node, we can replace all of them
3031 // with one ReplaceTextTransaction.
3032 if (endToNormalize.ContainerAs<Text>() ==
3033 aStartToDelete.ContainerAs<Text>()) {
3034 lengthInStartNode += lengthInEndNode;
3035 lengthInEndNode = 0;
3039 MOZ_ASSERT(lengthInStartNode + lengthInEndNode);
3041 // Next, generate normalized white-spaces.
3042 if (!lengthInEndNode) {
3043 HTMLEditor::GenerateWhiteSpaceSequence(
3044 aNormalizedWhiteSpacesInStartNode, lengthInStartNode,
3045 previousCharPointData, nextCharPointData);
3046 } else if (!lengthInStartNode) {
3047 HTMLEditor::GenerateWhiteSpaceSequence(
3048 aNormalizedWhiteSpacesInEndNode, lengthInEndNode, previousCharPointData,
3049 nextCharPointData);
3050 } else {
3051 // For making `GenerateWhiteSpaceSequence()` simpler, we should create
3052 // whole white-space sequence first, then, copy to the out params.
3053 nsAutoString whiteSpaces;
3054 HTMLEditor::GenerateWhiteSpaceSequence(
3055 whiteSpaces, lengthInStartNode + lengthInEndNode, previousCharPointData,
3056 nextCharPointData);
3057 aNormalizedWhiteSpacesInStartNode =
3058 Substring(whiteSpaces, 0, lengthInStartNode);
3059 aNormalizedWhiteSpacesInEndNode = Substring(whiteSpaces, lengthInStartNode);
3060 MOZ_ASSERT(aNormalizedWhiteSpacesInEndNode.Length() == lengthInEndNode);
3063 // TODO: Shrink the replacing range and string as far as possible because
3064 // this may run a lot, i.e., HTMLEditor creates ReplaceTextTransaction
3065 // a lot for normalizing white-spaces. Then, each transaction shouldn't
3066 // have all white-spaces every time because once it's normalized, we
3067 // don't need to normalize all of the sequence again, but currently
3068 // we do.
3070 // Finally, extend the range.
3071 if (startToNormalize.IsSet()) {
3072 aStartToDelete = startToNormalize;
3074 if (endToNormalize.IsSet()) {
3075 aEndToDelete = endToNormalize;
3079 Result<CaretPoint, nsresult>
3080 HTMLEditor::DeleteTextAndNormalizeSurroundingWhiteSpaces(
3081 const EditorDOMPointInText& aStartToDelete,
3082 const EditorDOMPointInText& aEndToDelete,
3083 TreatEmptyTextNodes aTreatEmptyTextNodes,
3084 DeleteDirection aDeleteDirection) {
3085 MOZ_ASSERT(aStartToDelete.IsSetAndValid());
3086 MOZ_ASSERT(aEndToDelete.IsSetAndValid());
3087 MOZ_ASSERT(aStartToDelete.EqualsOrIsBefore(aEndToDelete));
3089 // Use nsString for these replacing string because we should avoid to copy
3090 // the buffer from auto storange to ReplaceTextTransaction.
3091 nsString normalizedWhiteSpacesInFirstNode, normalizedWhiteSpacesInLastNode;
3093 // First, check whether we need to normalize white-spaces after deleting
3094 // the given range.
3095 EditorDOMPointInText startToDelete(aStartToDelete);
3096 EditorDOMPointInText endToDelete(aEndToDelete);
3097 ExtendRangeToDeleteWithNormalizingWhiteSpaces(
3098 startToDelete, endToDelete, normalizedWhiteSpacesInFirstNode,
3099 normalizedWhiteSpacesInLastNode);
3101 // If extended range is still collapsed, i.e., the caller just wants to
3102 // normalize white-space sequence, but there is no white-spaces which need to
3103 // be replaced, we need to do nothing here.
3104 if (startToDelete == endToDelete) {
3105 return CaretPoint(aStartToDelete.To<EditorDOMPoint>());
3108 // Note that the container text node of startToDelete may be removed from
3109 // the tree if it becomes empty. Therefore, we need to track the point.
3110 EditorDOMPoint newCaretPosition;
3111 if (aStartToDelete.ContainerAs<Text>() == aEndToDelete.ContainerAs<Text>()) {
3112 newCaretPosition = aEndToDelete.To<EditorDOMPoint>();
3113 } else if (aDeleteDirection == DeleteDirection::Forward) {
3114 newCaretPosition.SetToEndOf(aStartToDelete.ContainerAs<Text>());
3115 } else {
3116 newCaretPosition.Set(aEndToDelete.ContainerAs<Text>(), 0u);
3119 // Then, modify the text nodes in the range.
3120 while (true) {
3121 AutoTrackDOMPoint trackingNewCaretPosition(RangeUpdaterRef(),
3122 &newCaretPosition);
3123 // Use ReplaceTextTransaction if we need to normalize white-spaces in
3124 // the first text node.
3125 if (!normalizedWhiteSpacesInFirstNode.IsEmpty()) {
3126 EditorDOMPoint trackingEndToDelete(endToDelete.ContainerAs<Text>(),
3127 endToDelete.Offset());
3129 AutoTrackDOMPoint trackEndToDelete(RangeUpdaterRef(),
3130 &trackingEndToDelete);
3131 uint32_t lengthToReplaceInFirstTextNode =
3132 startToDelete.ContainerAs<Text>() ==
3133 trackingEndToDelete.ContainerAs<Text>()
3134 ? trackingEndToDelete.Offset() - startToDelete.Offset()
3135 : startToDelete.ContainerAs<Text>()->TextLength() -
3136 startToDelete.Offset();
3137 Result<InsertTextResult, nsresult> replaceTextResult =
3138 ReplaceTextWithTransaction(
3139 MOZ_KnownLive(*startToDelete.ContainerAs<Text>()),
3140 startToDelete.Offset(), lengthToReplaceInFirstTextNode,
3141 normalizedWhiteSpacesInFirstNode);
3142 if (MOZ_UNLIKELY(replaceTextResult.isErr())) {
3143 NS_WARNING("HTMLEditor::ReplaceTextWithTransaction() failed");
3144 return replaceTextResult.propagateErr();
3146 // We'll return computed caret point, newCaretPosition, below.
3147 replaceTextResult.unwrap().IgnoreCaretPointSuggestion();
3148 if (startToDelete.ContainerAs<Text>() ==
3149 trackingEndToDelete.ContainerAs<Text>()) {
3150 MOZ_ASSERT(normalizedWhiteSpacesInLastNode.IsEmpty());
3151 break; // There is no more text which we need to delete.
3154 if (MayHaveMutationEventListeners(
3155 NS_EVENT_BITS_MUTATION_CHARACTERDATAMODIFIED) &&
3156 (NS_WARN_IF(!trackingEndToDelete.IsSetAndValid()) ||
3157 NS_WARN_IF(!trackingEndToDelete.IsInTextNode()))) {
3158 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
3160 MOZ_ASSERT(trackingEndToDelete.IsInTextNode());
3161 endToDelete.Set(trackingEndToDelete.ContainerAs<Text>(),
3162 trackingEndToDelete.Offset());
3163 // If the remaining range was modified by mutation event listener,
3164 // we should stop handling the deletion.
3165 startToDelete =
3166 EditorDOMPointInText::AtEndOf(*startToDelete.ContainerAs<Text>());
3167 if (MayHaveMutationEventListeners(
3168 NS_EVENT_BITS_MUTATION_CHARACTERDATAMODIFIED) &&
3169 NS_WARN_IF(!startToDelete.IsBefore(endToDelete))) {
3170 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
3173 // Delete ASCII whiteSpaces in the range simpley if there are some text
3174 // nodes which we don't need to replace their text.
3175 if (normalizedWhiteSpacesInLastNode.IsEmpty() ||
3176 startToDelete.ContainerAs<Text>() != endToDelete.ContainerAs<Text>()) {
3177 // If we need to replace text in the last text node, we should
3178 // delete text before its previous text node.
3179 EditorDOMPointInText endToDeleteExceptReplaceRange =
3180 normalizedWhiteSpacesInLastNode.IsEmpty()
3181 ? endToDelete
3182 : EditorDOMPointInText(endToDelete.ContainerAs<Text>(), 0);
3183 if (startToDelete != endToDeleteExceptReplaceRange) {
3184 Result<CaretPoint, nsresult> caretPointOrError =
3185 DeleteTextAndTextNodesWithTransaction(startToDelete,
3186 endToDeleteExceptReplaceRange,
3187 aTreatEmptyTextNodes);
3188 if (MOZ_UNLIKELY(caretPointOrError.isErr())) {
3189 NS_WARNING(
3190 "HTMLEditor::DeleteTextAndTextNodesWithTransaction() failed");
3191 return caretPointOrError.propagateErr();
3193 nsresult rv = caretPointOrError.unwrap().SuggestCaretPointTo(
3194 *this, {SuggestCaret::OnlyIfHasSuggestion,
3195 SuggestCaret::OnlyIfTransactionsAllowedToDoIt,
3196 SuggestCaret::AndIgnoreTrivialError});
3197 if (NS_FAILED(rv)) {
3198 NS_WARNING("CaretPoint::SuggestCaretPointTo() failed");
3199 return Err(rv);
3201 NS_WARNING_ASSERTION(
3202 rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
3203 "CaretPoint::SuggestCaretPointTo() failed, but ignored");
3204 if (normalizedWhiteSpacesInLastNode.IsEmpty()) {
3205 break; // There is no more text which we need to delete.
3207 if (MayHaveMutationEventListeners(
3208 NS_EVENT_BITS_MUTATION_CHARACTERDATAMODIFIED |
3209 NS_EVENT_BITS_MUTATION_NODEREMOVED |
3210 NS_EVENT_BITS_MUTATION_NODEREMOVEDFROMDOCUMENT |
3211 NS_EVENT_BITS_MUTATION_SUBTREEMODIFIED) &&
3212 (NS_WARN_IF(!endToDeleteExceptReplaceRange.IsSetAndValid()) ||
3213 NS_WARN_IF(!endToDelete.IsSetAndValid()) ||
3214 NS_WARN_IF(endToDelete.IsStartOfContainer()))) {
3215 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
3217 // Then, replace the text in the last text node.
3218 startToDelete = endToDeleteExceptReplaceRange;
3222 // Replace ASCII whiteSpaces in the range and following character in the
3223 // last text node.
3224 MOZ_ASSERT(!normalizedWhiteSpacesInLastNode.IsEmpty());
3225 MOZ_ASSERT(startToDelete.ContainerAs<Text>() ==
3226 endToDelete.ContainerAs<Text>());
3227 Result<InsertTextResult, nsresult> replaceTextResult =
3228 ReplaceTextWithTransaction(
3229 MOZ_KnownLive(*startToDelete.ContainerAs<Text>()),
3230 startToDelete.Offset(),
3231 endToDelete.Offset() - startToDelete.Offset(),
3232 normalizedWhiteSpacesInLastNode);
3233 if (MOZ_UNLIKELY(replaceTextResult.isErr())) {
3234 NS_WARNING("HTMLEditor::ReplaceTextWithTransaction() failed");
3235 return replaceTextResult.propagateErr();
3237 // We'll return computed caret point, newCaretPosition, below.
3238 replaceTextResult.unwrap().IgnoreCaretPointSuggestion();
3239 break;
3242 if (NS_WARN_IF(!newCaretPosition.IsSetAndValid()) ||
3243 NS_WARN_IF(!newCaretPosition.GetContainer()->IsInComposedDoc())) {
3244 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
3247 // Look for leaf node to put caret if we remove some empty inline ancestors
3248 // at new caret position.
3249 if (!newCaretPosition.IsInTextNode()) {
3250 if (const Element* editableBlockElementOrInlineEditingHost =
3251 HTMLEditUtils::GetInclusiveAncestorElement(
3252 *newCaretPosition.ContainerAs<nsIContent>(),
3253 HTMLEditUtils::ClosestEditableBlockElementOrInlineEditingHost,
3254 BlockInlineCheck::UseComputedDisplayStyle)) {
3255 Element* editingHost = ComputeEditingHost();
3256 // Try to put caret next to immediately after previous editable leaf.
3257 nsIContent* previousContent =
3258 HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement(
3259 newCaretPosition, *editableBlockElementOrInlineEditingHost,
3260 {LeafNodeType::LeafNodeOrNonEditableNode},
3261 BlockInlineCheck::UseComputedDisplayStyle, editingHost);
3262 if (previousContent &&
3263 !HTMLEditUtils::IsBlockElement(
3264 *previousContent, BlockInlineCheck::UseComputedDisplayStyle)) {
3265 newCaretPosition =
3266 previousContent->IsText() ||
3267 HTMLEditUtils::IsContainerNode(*previousContent)
3268 ? EditorDOMPoint::AtEndOf(*previousContent)
3269 : EditorDOMPoint::After(*previousContent);
3271 // But if the point is very first of a block element or immediately after
3272 // a child block, look for next editable leaf instead.
3273 else if (nsIContent* nextContent =
3274 HTMLEditUtils::GetNextLeafContentOrNextBlockElement(
3275 newCaretPosition,
3276 *editableBlockElementOrInlineEditingHost,
3277 {LeafNodeType::LeafNodeOrNonEditableNode},
3278 BlockInlineCheck::UseComputedDisplayStyle,
3279 editingHost)) {
3280 newCaretPosition = nextContent->IsText() ||
3281 HTMLEditUtils::IsContainerNode(*nextContent)
3282 ? EditorDOMPoint(nextContent, 0)
3283 : EditorDOMPoint(nextContent);
3288 // For compatibility with Blink, we should move caret to end of previous
3289 // text node if it's direct previous sibling of the first text node in the
3290 // range.
3291 if (newCaretPosition.IsStartOfContainer() &&
3292 newCaretPosition.IsInTextNode() &&
3293 newCaretPosition.GetContainer()->GetPreviousSibling() &&
3294 newCaretPosition.GetContainer()->GetPreviousSibling()->IsText()) {
3295 newCaretPosition.SetToEndOf(
3296 newCaretPosition.GetContainer()->GetPreviousSibling()->AsText());
3300 AutoTrackDOMPoint trackingNewCaretPosition(RangeUpdaterRef(),
3301 &newCaretPosition);
3302 Result<CaretPoint, nsresult> caretPointOrError =
3303 InsertBRElementIfHardLineIsEmptyAndEndsWithBlockBoundary(
3304 newCaretPosition);
3305 if (MOZ_UNLIKELY(caretPointOrError.isErr())) {
3306 NS_WARNING(
3307 "HTMLEditor::"
3308 "InsertBRElementIfHardLineIsEmptyAndEndsWithBlockBoundary() failed");
3309 return caretPointOrError;
3312 if (!newCaretPosition.IsSetAndValid()) {
3313 NS_WARNING("Inserting <br> element caused unexpected DOM tree");
3314 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
3316 return CaretPoint(std::move(newCaretPosition));
3319 Result<CaretPoint, nsresult>
3320 HTMLEditor::InsertBRElementIfHardLineIsEmptyAndEndsWithBlockBoundary(
3321 const EditorDOMPoint& aPointToInsert) {
3322 MOZ_ASSERT(IsEditActionDataAvailable());
3323 MOZ_ASSERT(aPointToInsert.IsSet());
3325 if (!aPointToInsert.IsInContentNode()) {
3326 return CaretPoint(EditorDOMPoint());
3329 // If container of the point is not in a block, we don't need to put a
3330 // `<br>` element here.
3331 if (!HTMLEditUtils::IsBlockElement(
3332 *aPointToInsert.ContainerAs<nsIContent>(),
3333 BlockInlineCheck::UseComputedDisplayStyle)) {
3334 return CaretPoint(EditorDOMPoint());
3337 if (NS_WARN_IF(!HTMLEditUtils::IsSimplyEditableNode(
3338 *aPointToInsert.ContainerAs<nsIContent>()))) {
3339 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
3342 WSRunScanner wsRunScanner(ComputeEditingHost(), aPointToInsert,
3343 BlockInlineCheck::UseComputedDisplayStyle);
3344 // If the point is not start of a hard line, we don't need to put a `<br>`
3345 // element here.
3346 if (!wsRunScanner.StartsFromHardLineBreak()) {
3347 return CaretPoint(EditorDOMPoint());
3349 // If the point is not end of a hard line or the hard line does not end with
3350 // block boundary, we don't need to put a `<br>` element here.
3351 if (!wsRunScanner.EndsByBlockBoundary()) {
3352 return CaretPoint(EditorDOMPoint());
3355 // If we cannot insert a `<br>` element here, do nothing.
3356 if (!HTMLEditUtils::CanNodeContain(*aPointToInsert.GetContainer(),
3357 *nsGkAtoms::br)) {
3358 return CaretPoint(EditorDOMPoint());
3361 Result<CreateElementResult, nsresult> insertBRElementResult = InsertBRElement(
3362 WithTransaction::Yes, aPointToInsert, nsIEditor::ePrevious);
3363 if (MOZ_UNLIKELY(insertBRElementResult.isErr())) {
3364 NS_WARNING(
3365 "HTMLEditor::InsertBRElement(WithTransaction::Yes, ePrevious) failed");
3366 return insertBRElementResult.propagateErr();
3368 return CaretPoint(insertBRElementResult.unwrap().UnwrapCaretPoint());
3371 Result<EditActionResult, nsresult>
3372 HTMLEditor::MakeOrChangeListAndListItemAsSubAction(
3373 const nsStaticAtom& aListElementOrListItemElementTagName,
3374 const nsAString& aBulletType,
3375 SelectAllOfCurrentList aSelectAllOfCurrentList) {
3376 MOZ_ASSERT(IsEditActionDataAvailable());
3377 MOZ_ASSERT(&aListElementOrListItemElementTagName == nsGkAtoms::ul ||
3378 &aListElementOrListItemElementTagName == nsGkAtoms::ol ||
3379 &aListElementOrListItemElementTagName == nsGkAtoms::dl ||
3380 &aListElementOrListItemElementTagName == nsGkAtoms::dd ||
3381 &aListElementOrListItemElementTagName == nsGkAtoms::dt);
3383 if (NS_WARN_IF(!mInitSucceeded)) {
3384 return Err(NS_ERROR_NOT_INITIALIZED);
3388 Result<EditActionResult, nsresult> result = CanHandleHTMLEditSubAction();
3389 if (MOZ_UNLIKELY(result.isErr())) {
3390 NS_WARNING("HTMLEditor::CanHandleHTMLEditSubAction() failed");
3391 return result;
3393 if (result.inspect().Canceled()) {
3394 return result;
3398 if (MOZ_UNLIKELY(IsSelectionRangeContainerNotContent())) {
3399 NS_WARNING("Some selection containers are not content node, but ignored");
3400 return EditActionResult::IgnoredResult();
3403 AutoPlaceholderBatch treatAsOneTransaction(
3404 *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
3406 // XXX EditSubAction::eCreateOrChangeDefinitionListItem and
3407 // EditSubAction::eCreateOrChangeList are treated differently in
3408 // HTMLEditor::MaybeSplitElementsAtEveryBRElement(). Only when
3409 // EditSubAction::eCreateOrChangeList, it splits inline nodes.
3410 // Currently, it shouldn't be done when we called for formatting
3411 // `<dd>` or `<dt>` by
3412 // HTMLEditor::MakeDefinitionListItemWithTransaction(). But this
3413 // difference may be a bug. We should investigate this later.
3414 IgnoredErrorResult error;
3415 AutoEditSubActionNotifier startToHandleEditSubAction(
3416 *this,
3417 &aListElementOrListItemElementTagName == nsGkAtoms::dd ||
3418 &aListElementOrListItemElementTagName == nsGkAtoms::dt
3419 ? EditSubAction::eCreateOrChangeDefinitionListItem
3420 : EditSubAction::eCreateOrChangeList,
3421 nsIEditor::eNext, error);
3422 if (NS_WARN_IF(error.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
3423 return Err(error.StealNSResult());
3425 NS_WARNING_ASSERTION(
3426 !error.Failed(),
3427 "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
3429 nsresult rv = EnsureNoPaddingBRElementForEmptyEditor();
3430 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
3431 return Err(NS_ERROR_EDITOR_DESTROYED);
3433 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
3434 "EditorBase::EnsureNoPaddingBRElementForEmptyEditor() "
3435 "failed, but ignored");
3437 if (NS_SUCCEEDED(rv) && SelectionRef().IsCollapsed()) {
3438 nsresult rv = EnsureCaretNotAfterInvisibleBRElement();
3439 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
3440 return Err(NS_ERROR_EDITOR_DESTROYED);
3442 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
3443 "HTMLEditor::EnsureCaretNotAfterInvisibleBRElement() "
3444 "failed, but ignored");
3445 if (NS_SUCCEEDED(rv)) {
3446 nsresult rv = PrepareInlineStylesForCaret();
3447 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
3448 return Err(NS_ERROR_EDITOR_DESTROYED);
3450 NS_WARNING_ASSERTION(
3451 NS_SUCCEEDED(rv),
3452 "HTMLEditor::PrepareInlineStylesForCaret() failed, but ignored");
3456 const nsStaticAtom* listTagName = nullptr;
3457 const nsStaticAtom* listItemTagName = nullptr;
3458 if (&aListElementOrListItemElementTagName == nsGkAtoms::ul ||
3459 &aListElementOrListItemElementTagName == nsGkAtoms::ol) {
3460 listTagName = &aListElementOrListItemElementTagName;
3461 listItemTagName = nsGkAtoms::li;
3462 } else if (&aListElementOrListItemElementTagName == nsGkAtoms::dl) {
3463 listTagName = &aListElementOrListItemElementTagName;
3464 listItemTagName = nsGkAtoms::dd;
3465 } else if (&aListElementOrListItemElementTagName == nsGkAtoms::dd ||
3466 &aListElementOrListItemElementTagName == nsGkAtoms::dt) {
3467 listTagName = nsGkAtoms::dl;
3468 listItemTagName = &aListElementOrListItemElementTagName;
3469 } else {
3470 NS_WARNING(
3471 "aListElementOrListItemElementTagName was neither list element name "
3472 "nor "
3473 "definition listitem element name");
3474 return Err(NS_ERROR_INVALID_ARG);
3477 const RefPtr<Element> editingHost = ComputeEditingHost();
3478 if (MOZ_UNLIKELY(!editingHost)) {
3479 return EditActionResult::CanceledResult();
3482 // Expands selection range to include the immediate block parent, and then
3483 // further expands to include any ancestors whose children are all in the
3484 // range.
3485 // XXX Why do we do this only when there is only one selection range?
3486 if (!SelectionRef().IsCollapsed() && SelectionRef().RangeCount() == 1u) {
3487 Result<EditorRawDOMRange, nsresult> extendedRange =
3488 GetRangeExtendedToHardLineEdgesForBlockEditAction(
3489 SelectionRef().GetRangeAt(0u), *editingHost);
3490 if (MOZ_UNLIKELY(extendedRange.isErr())) {
3491 NS_WARNING(
3492 "HTMLEditor::GetRangeExtendedToHardLineEdgesForBlockEditAction() "
3493 "failed");
3494 return extendedRange.propagateErr();
3496 // Note that end point may be prior to start point. So, we
3497 // cannot use Selection::SetStartAndEndInLimit() here.
3498 error.SuppressException();
3499 SelectionRef().SetBaseAndExtentInLimiter(
3500 extendedRange.inspect().StartRef().ToRawRangeBoundary(),
3501 extendedRange.inspect().EndRef().ToRawRangeBoundary(), error);
3502 if (NS_WARN_IF(Destroyed())) {
3503 return Err(NS_ERROR_EDITOR_DESTROYED);
3505 if (MOZ_UNLIKELY(error.Failed())) {
3506 NS_WARNING("Selection::SetBaseAndExtentInLimiter() failed");
3507 return Err(error.StealNSResult());
3511 AutoListElementCreator listCreator(*listTagName, *listItemTagName,
3512 aBulletType);
3513 AutoRangeArray selectionRanges(SelectionRef());
3514 Result<EditActionResult, nsresult> result = listCreator.Run(
3515 *this, selectionRanges, aSelectAllOfCurrentList, *editingHost);
3516 if (MOZ_UNLIKELY(result.isErr())) {
3517 NS_WARNING("HTMLEditor::ConvertContentAroundRangesToList() failed");
3518 // XXX Should we try to restore selection ranges in this case?
3519 return result;
3522 rv = selectionRanges.ApplyTo(SelectionRef());
3523 if (NS_WARN_IF(Destroyed())) {
3524 return Err(NS_ERROR_EDITOR_DESTROYED);
3526 if (NS_FAILED(rv)) {
3527 NS_WARNING("AutoRangeArray::ApplyTo() failed");
3528 return Err(rv);
3530 return result.inspect().Ignored() ? EditActionResult::CanceledResult()
3531 : EditActionResult::HandledResult();
3534 Result<EditActionResult, nsresult> HTMLEditor::AutoListElementCreator::Run(
3535 HTMLEditor& aHTMLEditor, AutoRangeArray& aRanges,
3536 SelectAllOfCurrentList aSelectAllOfCurrentList,
3537 const Element& aEditingHost) const {
3538 MOZ_ASSERT(aHTMLEditor.IsTopLevelEditSubActionDataAvailable());
3539 MOZ_ASSERT(!aHTMLEditor.IsSelectionRangeContainerNotContent());
3541 if (NS_WARN_IF(!aRanges.SaveAndTrackRanges(aHTMLEditor))) {
3542 return Err(NS_ERROR_FAILURE);
3545 AutoContentNodeArray arrayOfContents;
3546 nsresult rv = SplitAtRangeEdgesAndCollectContentNodesToMoveIntoList(
3547 aHTMLEditor, aRanges, aSelectAllOfCurrentList, aEditingHost,
3548 arrayOfContents);
3549 if (NS_FAILED(rv)) {
3550 NS_WARNING(
3551 "AutoListElementCreator::"
3552 "SplitAtRangeEdgesAndCollectContentNodesToMoveIntoList() failed");
3553 return Err(rv);
3556 // check if all our nodes are <br>s, or empty inlines
3557 // if no nodes, we make empty list. Ditto if the user tried to make a list
3558 // of some # of breaks.
3559 if (AutoListElementCreator::
3560 IsEmptyOrContainsOnlyBRElementsOrEmptyInlineElements(
3561 arrayOfContents)) {
3562 Result<RefPtr<Element>, nsresult> newListItemElementOrError =
3563 ReplaceContentNodesWithEmptyNewList(aHTMLEditor, aRanges,
3564 arrayOfContents, aEditingHost);
3565 if (MOZ_UNLIKELY(newListItemElementOrError.isErr())) {
3566 NS_WARNING(
3567 "AutoListElementCreator::ReplaceContentNodesWithEmptyNewList() "
3568 "failed");
3569 return newListItemElementOrError.propagateErr();
3571 if (MOZ_UNLIKELY(!newListItemElementOrError.inspect())) {
3572 aRanges.RestoreFromSavedRanges();
3573 return EditActionResult::CanceledResult();
3575 aRanges.ClearSavedRanges();
3576 nsresult rv = aRanges.Collapse(
3577 EditorRawDOMPoint(newListItemElementOrError.inspect(), 0u));
3578 if (NS_FAILED(rv)) {
3579 NS_WARNING("AutoRangeArray::Collapse() failed");
3580 return Err(rv);
3582 return EditActionResult::IgnoredResult();
3585 Result<RefPtr<Element>, nsresult> listItemOrListToPutCaretOrError =
3586 WrapContentNodesIntoNewListElements(aHTMLEditor, aRanges, arrayOfContents,
3587 aEditingHost);
3588 if (MOZ_UNLIKELY(listItemOrListToPutCaretOrError.isErr())) {
3589 NS_WARNING(
3590 "AutoListElementCreator::WrapContentNodesIntoNewListElements() failed");
3591 return listItemOrListToPutCaretOrError.propagateErr();
3594 MOZ_ASSERT(aRanges.HasSavedRanges());
3595 aRanges.RestoreFromSavedRanges();
3597 // If selection will be collapsed but not in listItemOrListToPutCaret, we need
3598 // to adjust the caret position into it.
3599 if (listItemOrListToPutCaretOrError.inspect()) {
3600 DebugOnly<nsresult> rvIgnored =
3601 EnsureCollapsedRangeIsInListItemOrListElement(
3602 *listItemOrListToPutCaretOrError.inspect(), aRanges);
3603 NS_WARNING_ASSERTION(
3604 NS_SUCCEEDED(rvIgnored),
3605 "AutoListElementCreator::"
3606 "EnsureCollapsedRangeIsInListItemOrListElement() failed, but ignored");
3609 return EditActionResult::HandledResult();
3612 nsresult HTMLEditor::AutoListElementCreator::
3613 SplitAtRangeEdgesAndCollectContentNodesToMoveIntoList(
3614 HTMLEditor& aHTMLEditor, AutoRangeArray& aRanges,
3615 SelectAllOfCurrentList aSelectAllOfCurrentList,
3616 const Element& aEditingHost,
3617 ContentNodeArray& aOutArrayOfContents) const {
3618 MOZ_ASSERT(aOutArrayOfContents.IsEmpty());
3620 if (aSelectAllOfCurrentList == SelectAllOfCurrentList::Yes) {
3621 if (Element* parentListElementOfRanges =
3622 aRanges.GetClosestAncestorAnyListElementOfRange()) {
3623 aOutArrayOfContents.AppendElement(
3624 OwningNonNull<nsIContent>(*parentListElementOfRanges));
3625 return NS_OK;
3629 AutoRangeArray extendedRanges(aRanges);
3631 // TODO: We don't need AutoTransactionsConserveSelection here in the
3632 // normal cases, but removing this may cause the behavior with the
3633 // legacy mutation event listeners. We should try to delete this in
3634 // a bug.
3635 AutoTransactionsConserveSelection dontChangeMySelection(aHTMLEditor);
3637 extendedRanges.ExtendRangesToWrapLines(EditSubAction::eCreateOrChangeList,
3638 BlockInlineCheck::UseHTMLDefaultStyle,
3639 aEditingHost);
3640 Result<EditorDOMPoint, nsresult> splitResult =
3641 extendedRanges.SplitTextAtEndBoundariesAndInlineAncestorsAtBothBoundaries(
3642 aHTMLEditor, BlockInlineCheck::UseHTMLDefaultStyle, aEditingHost);
3643 if (MOZ_UNLIKELY(splitResult.isErr())) {
3644 NS_WARNING(
3645 "AutoRangeArray::"
3646 "SplitTextAtEndBoundariesAndInlineAncestorsAtBothBoundaries() failed");
3647 return splitResult.unwrapErr();
3649 nsresult rv = extendedRanges.CollectEditTargetNodes(
3650 aHTMLEditor, aOutArrayOfContents, EditSubAction::eCreateOrChangeList,
3651 AutoRangeArray::CollectNonEditableNodes::No);
3652 if (NS_FAILED(rv)) {
3653 NS_WARNING(
3654 "AutoRangeArray::CollectEditTargetNodes(EditSubAction::"
3655 "eCreateOrChangeList, CollectNonEditableNodes::No) failed");
3656 return rv;
3659 Result<EditorDOMPoint, nsresult> splitAtBRElementsResult =
3660 aHTMLEditor.MaybeSplitElementsAtEveryBRElement(
3661 aOutArrayOfContents, EditSubAction::eCreateOrChangeList);
3662 if (MOZ_UNLIKELY(splitAtBRElementsResult.isErr())) {
3663 NS_WARNING(
3664 "HTMLEditor::MaybeSplitElementsAtEveryBRElement(EditSubAction::"
3665 "eCreateOrChangeList) failed");
3666 return splitAtBRElementsResult.unwrapErr();
3668 return NS_OK;
3671 // static
3672 bool HTMLEditor::AutoListElementCreator::
3673 IsEmptyOrContainsOnlyBRElementsOrEmptyInlineElements(
3674 const ContentNodeArray& aArrayOfContents) {
3675 for (const OwningNonNull<nsIContent>& content : aArrayOfContents) {
3676 // if content is not a <br> or empty inline, we're done
3677 // XXX Should we handle line breaks in preformatted text node?
3678 if (!content->IsHTMLElement(nsGkAtoms::br) &&
3679 !HTMLEditUtils::IsEmptyInlineContainer(
3680 content,
3681 {EmptyCheckOption::TreatSingleBRElementAsVisible,
3682 EmptyCheckOption::TreatNonEditableContentAsInvisible},
3683 BlockInlineCheck::UseComputedDisplayStyle)) {
3684 return false;
3687 return true;
3690 Result<RefPtr<Element>, nsresult>
3691 HTMLEditor::AutoListElementCreator::ReplaceContentNodesWithEmptyNewList(
3692 HTMLEditor& aHTMLEditor, const AutoRangeArray& aRanges,
3693 const AutoContentNodeArray& aArrayOfContents,
3694 const Element& aEditingHost) const {
3695 // if only breaks, delete them
3696 for (const OwningNonNull<nsIContent>& content : aArrayOfContents) {
3697 // MOZ_KnownLive because of bug 1620312
3698 nsresult rv =
3699 aHTMLEditor.DeleteNodeWithTransaction(MOZ_KnownLive(*content));
3700 if (NS_FAILED(rv)) {
3701 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
3702 return Err(rv);
3706 const auto firstRangeStartPoint =
3707 aRanges.GetFirstRangeStartPoint<EditorDOMPoint>();
3708 if (NS_WARN_IF(!firstRangeStartPoint.IsSet())) {
3709 return Err(NS_ERROR_FAILURE);
3712 // Make sure we can put a list here.
3713 if (!HTMLEditUtils::CanNodeContain(*firstRangeStartPoint.GetContainer(),
3714 mListTagName)) {
3715 return RefPtr<Element>();
3718 RefPtr<Element> newListItemElement;
3719 Result<CreateElementResult, nsresult> createNewListElementResult =
3720 aHTMLEditor.InsertElementWithSplittingAncestorsWithTransaction(
3721 mListTagName, firstRangeStartPoint, BRElementNextToSplitPoint::Keep,
3722 aEditingHost,
3723 // MOZ_CAN_RUN_SCRIPT_BOUNDARY due to bug 1758868
3724 [&](HTMLEditor& aHTMLEditor, Element& aListElement,
3725 const EditorDOMPoint&) MOZ_CAN_RUN_SCRIPT_BOUNDARY {
3726 AutoHandlingState dummyState;
3727 Result<CreateElementResult, nsresult> createListItemElementResult =
3728 AppendListItemElement(aHTMLEditor, aListElement, dummyState);
3729 if (MOZ_UNLIKELY(createListItemElementResult.isErr())) {
3730 NS_WARNING(
3731 "AutoListElementCreator::AppendListItemElement() failed");
3732 return createListItemElementResult.unwrapErr();
3734 CreateElementResult unwrappedResult =
3735 createListItemElementResult.unwrap();
3736 // There is AutoSelectionRestorer in this method so that it'll
3737 // be restored or updated with making it abort. Therefore,
3738 // we don't need to update selection here.
3739 // XXX I'd like to check aRanges.HasSavedRanges() here, but it
3740 // requires ifdefs to avoid bustage of opt builds caused
3741 // by unused warning...
3742 unwrappedResult.IgnoreCaretPointSuggestion();
3743 newListItemElement = unwrappedResult.UnwrapNewNode();
3744 MOZ_ASSERT(newListItemElement);
3745 return NS_OK;
3747 if (MOZ_UNLIKELY(createNewListElementResult.isErr())) {
3748 NS_WARNING(
3749 nsPrintfCString(
3750 "HTMLEditor::InsertElementWithSplittingAncestorsWithTransaction("
3751 "%s) failed",
3752 nsAtomCString(&mListTagName).get())
3753 .get());
3754 return createNewListElementResult.propagateErr();
3756 MOZ_ASSERT(createNewListElementResult.inspect().GetNewNode());
3758 // Put selection in new list item and don't restore the Selection.
3759 createNewListElementResult.inspect().IgnoreCaretPointSuggestion();
3760 return newListItemElement;
3763 Result<RefPtr<Element>, nsresult>
3764 HTMLEditor::AutoListElementCreator::WrapContentNodesIntoNewListElements(
3765 HTMLEditor& aHTMLEditor, AutoRangeArray& aRanges,
3766 AutoContentNodeArray& aArrayOfContents, const Element& aEditingHost) const {
3767 // if there is only one node in the array, and it is a list, div, or
3768 // blockquote, then look inside of it until we find inner list or content.
3769 if (aArrayOfContents.Length() == 1) {
3770 if (Element* deepestDivBlockquoteOrListElement =
3771 HTMLEditUtils::GetInclusiveDeepestFirstChildWhichHasOneChild(
3772 aArrayOfContents[0], {WalkTreeOption::IgnoreNonEditableNode},
3773 BlockInlineCheck::UseHTMLDefaultStyle, nsGkAtoms::div,
3774 nsGkAtoms::blockquote, nsGkAtoms::ul, nsGkAtoms::ol,
3775 nsGkAtoms::dl)) {
3776 if (deepestDivBlockquoteOrListElement->IsAnyOfHTMLElements(
3777 nsGkAtoms::div, nsGkAtoms::blockquote)) {
3778 aArrayOfContents.Clear();
3779 HTMLEditUtils::CollectChildren(*deepestDivBlockquoteOrListElement,
3780 aArrayOfContents, 0, {});
3781 } else {
3782 aArrayOfContents.ReplaceElementAt(
3783 0, OwningNonNull<nsIContent>(*deepestDivBlockquoteOrListElement));
3788 // Ok, now go through all the nodes and put then in the list,
3789 // or whatever is appropriate. Wohoo!
3790 AutoHandlingState handlingState;
3791 for (const OwningNonNull<nsIContent>& content : aArrayOfContents) {
3792 // MOZ_KnownLive because of bug 1620312
3793 nsresult rv = HandleChildContent(aHTMLEditor, MOZ_KnownLive(content),
3794 handlingState, aEditingHost);
3795 if (NS_FAILED(rv)) {
3796 NS_WARNING("AutoListElementCreator::HandleChildContent() failed");
3797 return Err(rv);
3801 return std::move(handlingState.mListOrListItemElementToPutCaret);
3804 nsresult HTMLEditor::AutoListElementCreator::HandleChildContent(
3805 HTMLEditor& aHTMLEditor, nsIContent& aHandlingContent,
3806 AutoHandlingState& aState, const Element& aEditingHost) const {
3807 // make sure we don't assemble content that is in different table cells
3808 // into the same list. respect table cell boundaries when listifying.
3809 if (aState.mCurrentListElement &&
3810 HTMLEditUtils::GetInclusiveAncestorAnyTableElement(
3811 *aState.mCurrentListElement) !=
3812 HTMLEditUtils::GetInclusiveAncestorAnyTableElement(
3813 aHandlingContent)) {
3814 aState.mCurrentListElement = nullptr;
3817 // If current node is a `<br>` element, delete it and forget previous
3818 // list item element.
3819 // If current node is an empty inline node, just delete it.
3820 if (EditorUtils::IsEditableContent(aHandlingContent, EditorType::HTML) &&
3821 (aHandlingContent.IsHTMLElement(nsGkAtoms::br) ||
3822 HTMLEditUtils::IsEmptyInlineContainer(
3823 aHandlingContent,
3824 {EmptyCheckOption::TreatSingleBRElementAsVisible,
3825 EmptyCheckOption::TreatNonEditableContentAsInvisible},
3826 BlockInlineCheck::UseHTMLDefaultStyle))) {
3827 nsresult rv = aHTMLEditor.DeleteNodeWithTransaction(aHandlingContent);
3828 if (NS_FAILED(rv)) {
3829 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
3830 return rv;
3832 if (aHandlingContent.IsHTMLElement(nsGkAtoms::br)) {
3833 aState.mPreviousListItemElement = nullptr;
3835 return NS_OK;
3838 // If we meet a list, we can reuse it or convert it to the expected type list.
3839 if (HTMLEditUtils::IsAnyListElement(&aHandlingContent)) {
3840 nsresult rv = HandleChildListElement(
3841 aHTMLEditor, MOZ_KnownLive(*aHandlingContent.AsElement()), aState);
3842 NS_WARNING_ASSERTION(
3843 NS_SUCCEEDED(rv),
3844 "AutoListElementCreator::HandleChildListElement() failed");
3845 return rv;
3848 // We cannot handle nodes if not in element node.
3849 if (NS_WARN_IF(!aHandlingContent.GetParentElement())) {
3850 return NS_ERROR_FAILURE;
3853 // If we meet a list item, we can just move it to current list element or new
3854 // list element.
3855 if (HTMLEditUtils::IsListItem(&aHandlingContent)) {
3856 nsresult rv = HandleChildListItemElement(
3857 aHTMLEditor, MOZ_KnownLive(*aHandlingContent.AsElement()), aState);
3858 NS_WARNING_ASSERTION(
3859 NS_SUCCEEDED(rv),
3860 "AutoListElementCreator::HandleChildListItemElement() failed");
3861 return rv;
3864 // If we meet a <div> or a <p>, we want only its children to wrapping into
3865 // list element. Therefore, this call will call this recursively.
3866 if (aHandlingContent.IsAnyOfHTMLElements(nsGkAtoms::div, nsGkAtoms::p)) {
3867 nsresult rv = HandleChildDivOrParagraphElement(
3868 aHTMLEditor, MOZ_KnownLive(*aHandlingContent.AsElement()), aState,
3869 aEditingHost);
3870 NS_WARNING_ASSERTION(
3871 NS_SUCCEEDED(rv),
3872 "AutoListElementCreator::HandleChildDivOrParagraphElement() failed");
3873 return rv;
3876 // If we've not met a list element, create a list element and make it
3877 // current list element.
3878 if (!aState.mCurrentListElement) {
3879 nsresult rv = CreateAndUpdateCurrentListElement(
3880 aHTMLEditor, EditorDOMPoint(&aHandlingContent),
3881 EmptyListItem::NotCreate, aState, aEditingHost);
3882 if (NS_FAILED(rv)) {
3883 NS_WARNING("AutoListElementCreator::HandleChildInlineElement() failed");
3884 return rv;
3888 // If we meet an inline content, we want to move it to previously used list
3889 // item element or new list item element.
3890 if (HTMLEditUtils::IsInlineContent(aHandlingContent,
3891 BlockInlineCheck::UseHTMLDefaultStyle)) {
3892 nsresult rv =
3893 HandleChildInlineContent(aHTMLEditor, aHandlingContent, aState);
3894 NS_WARNING_ASSERTION(
3895 NS_SUCCEEDED(rv),
3896 "AutoListElementCreator::HandleChildInlineElement() failed");
3897 return rv;
3900 // Otherwise, we should wrap it into new list item element.
3901 nsresult rv =
3902 WrapContentIntoNewListItemElement(aHTMLEditor, aHandlingContent, aState);
3903 NS_WARNING_ASSERTION(
3904 NS_SUCCEEDED(rv),
3905 "AutoListElementCreator::WrapContentIntoNewListItemElement() failed");
3906 return rv;
3909 nsresult HTMLEditor::AutoListElementCreator::HandleChildListElement(
3910 HTMLEditor& aHTMLEditor, Element& aHandlingListElement,
3911 AutoHandlingState& aState) const {
3912 MOZ_ASSERT(HTMLEditUtils::IsAnyListElement(&aHandlingListElement));
3914 // If we met a list element and current list element is not a descendant
3915 // of the list, append current node to end of the current list element.
3916 // Then, wrap it with list item element and delete the old container.
3917 if (aState.mCurrentListElement &&
3918 !EditorUtils::IsDescendantOf(aHandlingListElement,
3919 *aState.mCurrentListElement)) {
3920 Result<MoveNodeResult, nsresult> moveNodeResult =
3921 aHTMLEditor.MoveNodeToEndWithTransaction(
3922 aHandlingListElement, MOZ_KnownLive(*aState.mCurrentListElement));
3923 if (MOZ_UNLIKELY(moveNodeResult.isErr())) {
3924 NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed");
3925 return moveNodeResult.propagateErr();
3927 moveNodeResult.inspect().IgnoreCaretPointSuggestion();
3929 Result<CreateElementResult, nsresult> convertListTypeResult =
3930 aHTMLEditor.ChangeListElementType(aHandlingListElement, mListTagName,
3931 mListItemTagName);
3932 if (MOZ_UNLIKELY(convertListTypeResult.isErr())) {
3933 NS_WARNING("HTMLEditor::ChangeListElementType() failed");
3934 return convertListTypeResult.propagateErr();
3936 convertListTypeResult.inspect().IgnoreCaretPointSuggestion();
3938 Result<EditorDOMPoint, nsresult> unwrapNewListElementResult =
3939 aHTMLEditor.RemoveBlockContainerWithTransaction(
3940 MOZ_KnownLive(*convertListTypeResult.inspect().GetNewNode()));
3941 if (MOZ_UNLIKELY(unwrapNewListElementResult.isErr())) {
3942 NS_WARNING("HTMLEditor::RemoveBlockContainerWithTransaction() failed");
3943 return unwrapNewListElementResult.propagateErr();
3945 aState.mPreviousListItemElement = nullptr;
3946 return NS_OK;
3949 // If current list element is in found list element or we've not met a
3950 // list element, convert current list element to proper type.
3951 Result<CreateElementResult, nsresult> convertListTypeResult =
3952 aHTMLEditor.ChangeListElementType(aHandlingListElement, mListTagName,
3953 mListItemTagName);
3954 if (MOZ_UNLIKELY(convertListTypeResult.isErr())) {
3955 NS_WARNING("HTMLEditor::ChangeListElementType() failed");
3956 return convertListTypeResult.propagateErr();
3958 CreateElementResult unwrappedConvertListTypeResult =
3959 convertListTypeResult.unwrap();
3960 unwrappedConvertListTypeResult.IgnoreCaretPointSuggestion();
3961 MOZ_ASSERT(unwrappedConvertListTypeResult.GetNewNode());
3962 aState.mCurrentListElement = unwrappedConvertListTypeResult.UnwrapNewNode();
3963 aState.mPreviousListItemElement = nullptr;
3964 return NS_OK;
3967 nsresult
3968 HTMLEditor::AutoListElementCreator::HandleChildListItemInDifferentTypeList(
3969 HTMLEditor& aHTMLEditor, Element& aHandlingListItemElement,
3970 AutoHandlingState& aState) const {
3971 MOZ_ASSERT(HTMLEditUtils::IsListItem(&aHandlingListItemElement));
3972 MOZ_ASSERT(
3973 !aHandlingListItemElement.GetParent()->IsHTMLElement(&mListTagName));
3975 // If we've not met a list element or current node is not in current list
3976 // element, insert a list element at current node and set current list element
3977 // to the new one.
3978 if (!aState.mCurrentListElement ||
3979 aHandlingListItemElement.IsInclusiveDescendantOf(
3980 aState.mCurrentListElement)) {
3981 EditorDOMPoint atListItem(&aHandlingListItemElement);
3982 MOZ_ASSERT(atListItem.IsInContentNode());
3984 Result<SplitNodeResult, nsresult> splitListItemParentResult =
3985 aHTMLEditor.SplitNodeWithTransaction(atListItem);
3986 if (MOZ_UNLIKELY(splitListItemParentResult.isErr())) {
3987 NS_WARNING("HTMLEditor::SplitNodeWithTransaction() failed");
3988 return splitListItemParentResult.propagateErr();
3990 SplitNodeResult unwrappedSplitListItemParentResult =
3991 splitListItemParentResult.unwrap();
3992 MOZ_ASSERT(unwrappedSplitListItemParentResult.DidSplit());
3993 unwrappedSplitListItemParentResult.IgnoreCaretPointSuggestion();
3995 Result<CreateElementResult, nsresult> createNewListElementResult =
3996 aHTMLEditor.CreateAndInsertElement(
3997 WithTransaction::Yes, mListTagName,
3998 unwrappedSplitListItemParentResult.AtNextContent<EditorDOMPoint>());
3999 if (MOZ_UNLIKELY(createNewListElementResult.isErr())) {
4000 NS_WARNING(
4001 "HTMLEditor::CreateAndInsertElement(WithTransaction::Yes) "
4002 "failed");
4003 return createNewListElementResult.propagateErr();
4005 CreateElementResult unwrapCreateNewListElementResult =
4006 createNewListElementResult.unwrap();
4007 unwrapCreateNewListElementResult.IgnoreCaretPointSuggestion();
4008 MOZ_ASSERT(unwrapCreateNewListElementResult.GetNewNode());
4009 aState.mCurrentListElement =
4010 unwrapCreateNewListElementResult.UnwrapNewNode();
4013 // Then, move current node into current list element.
4014 Result<MoveNodeResult, nsresult> moveNodeResult =
4015 aHTMLEditor.MoveNodeToEndWithTransaction(
4016 aHandlingListItemElement, MOZ_KnownLive(*aState.mCurrentListElement));
4017 if (MOZ_UNLIKELY(moveNodeResult.isErr())) {
4018 NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed");
4019 return moveNodeResult.propagateErr();
4021 moveNodeResult.inspect().IgnoreCaretPointSuggestion();
4023 // Convert list item type if current node is different list item type.
4024 if (aHandlingListItemElement.IsHTMLElement(&mListItemTagName)) {
4025 return NS_OK;
4027 Result<CreateElementResult, nsresult> newListItemElementOrError =
4028 aHTMLEditor.ReplaceContainerAndCloneAttributesWithTransaction(
4029 aHandlingListItemElement, mListItemTagName);
4030 if (MOZ_UNLIKELY(newListItemElementOrError.isErr())) {
4031 NS_WARNING("HTMLEditor::ReplaceContainerWithTransaction() failed");
4032 return newListItemElementOrError.propagateErr();
4034 newListItemElementOrError.inspect().IgnoreCaretPointSuggestion();
4035 return NS_OK;
4038 nsresult HTMLEditor::AutoListElementCreator::HandleChildListItemElement(
4039 HTMLEditor& aHTMLEditor, Element& aHandlingListItemElement,
4040 AutoHandlingState& aState) const {
4041 MOZ_ASSERT(aHandlingListItemElement.GetParentNode());
4042 MOZ_ASSERT(HTMLEditUtils::IsListItem(&aHandlingListItemElement));
4044 // If current list item element is not in proper list element, we need
4045 // to convert the list element.
4046 // XXX This check is not enough,
4047 if (!aHandlingListItemElement.GetParentNode()->IsHTMLElement(&mListTagName)) {
4048 nsresult rv = HandleChildListItemInDifferentTypeList(
4049 aHTMLEditor, aHandlingListItemElement, aState);
4050 if (NS_FAILED(rv)) {
4051 NS_WARNING(
4052 "AutoListElementCreator::HandleChildListItemInDifferentTypeList() "
4053 "failed");
4054 return rv;
4056 } else {
4057 nsresult rv = HandleChildListItemInSameTypeList(
4058 aHTMLEditor, aHandlingListItemElement, aState);
4059 if (NS_FAILED(rv)) {
4060 NS_WARNING(
4061 "AutoListElementCreator::HandleChildListItemInSameTypeList() failed");
4062 return rv;
4066 // If bullet type is specified, set list type attribute.
4067 // XXX Cannot we set type attribute before inserting the list item
4068 // element into the DOM tree?
4069 if (!mBulletType.IsEmpty()) {
4070 nsresult rv = aHTMLEditor.SetAttributeWithTransaction(
4071 aHandlingListItemElement, *nsGkAtoms::type, mBulletType);
4072 if (NS_WARN_IF(aHTMLEditor.Destroyed())) {
4073 return NS_ERROR_EDITOR_DESTROYED;
4075 NS_WARNING_ASSERTION(
4076 NS_SUCCEEDED(rv),
4077 "EditorBase::SetAttributeWithTransaction(nsGkAtoms::type) failed");
4078 return rv;
4081 // Otherwise, remove list type attribute if there is.
4082 if (!aHandlingListItemElement.HasAttr(nsGkAtoms::type)) {
4083 return NS_OK;
4085 nsresult rv = aHTMLEditor.RemoveAttributeWithTransaction(
4086 aHandlingListItemElement, *nsGkAtoms::type);
4087 NS_WARNING_ASSERTION(
4088 NS_SUCCEEDED(rv),
4089 "EditorBase::RemoveAttributeWithTransaction(nsGkAtoms::type) failed");
4090 return rv;
4093 nsresult HTMLEditor::AutoListElementCreator::HandleChildListItemInSameTypeList(
4094 HTMLEditor& aHTMLEditor, Element& aHandlingListItemElement,
4095 AutoHandlingState& aState) const {
4096 MOZ_ASSERT(HTMLEditUtils::IsListItem(&aHandlingListItemElement));
4097 MOZ_ASSERT(
4098 aHandlingListItemElement.GetParent()->IsHTMLElement(&mListTagName));
4100 EditorDOMPoint atListItem(&aHandlingListItemElement);
4101 MOZ_ASSERT(atListItem.IsInContentNode());
4103 // If we've not met a list element, set current list element to the
4104 // parent of current list item element.
4105 if (!aState.mCurrentListElement) {
4106 aState.mCurrentListElement = atListItem.GetContainerAs<Element>();
4107 NS_WARNING_ASSERTION(
4108 HTMLEditUtils::IsAnyListElement(aState.mCurrentListElement),
4109 "Current list item parent is not a list element");
4111 // If current list item element is not a child of current list element,
4112 // move it into current list item.
4113 else if (atListItem.GetContainer() != aState.mCurrentListElement) {
4114 Result<MoveNodeResult, nsresult> moveNodeResult =
4115 aHTMLEditor.MoveNodeToEndWithTransaction(
4116 aHandlingListItemElement,
4117 MOZ_KnownLive(*aState.mCurrentListElement));
4118 if (MOZ_UNLIKELY(moveNodeResult.isErr())) {
4119 NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed");
4120 return moveNodeResult.propagateErr();
4122 moveNodeResult.inspect().IgnoreCaretPointSuggestion();
4125 // Then, if current list item element is not proper type for current
4126 // list element, convert list item element to proper element.
4127 if (aHandlingListItemElement.IsHTMLElement(&mListItemTagName)) {
4128 return NS_OK;
4130 // FIXME: Manage attribute cloning
4131 Result<CreateElementResult, nsresult> newListItemElementOrError =
4132 aHTMLEditor.ReplaceContainerAndCloneAttributesWithTransaction(
4133 aHandlingListItemElement, mListItemTagName);
4134 if (MOZ_UNLIKELY(newListItemElementOrError.isErr())) {
4135 NS_WARNING(
4136 "HTMLEditor::ReplaceContainerAndCloneAttributesWithTransaction() "
4137 "failed");
4138 return newListItemElementOrError.propagateErr();
4140 newListItemElementOrError.inspect().IgnoreCaretPointSuggestion();
4141 return NS_OK;
4144 nsresult HTMLEditor::AutoListElementCreator::HandleChildDivOrParagraphElement(
4145 HTMLEditor& aHTMLEditor, Element& aHandlingDivOrParagraphElement,
4146 AutoHandlingState& aState, const Element& aEditingHost) const {
4147 MOZ_ASSERT(aHandlingDivOrParagraphElement.IsAnyOfHTMLElements(nsGkAtoms::div,
4148 nsGkAtoms::p));
4150 AutoRestore<RefPtr<Element>> previouslyReplacingBlockElement(
4151 aState.mReplacingBlockElement);
4152 aState.mReplacingBlockElement = &aHandlingDivOrParagraphElement;
4153 AutoRestore<bool> previouslyReplacingBlockElementIdCopied(
4154 aState.mMaybeCopiedReplacingBlockElementId);
4155 aState.mMaybeCopiedReplacingBlockElementId = false;
4157 // If the <div> or <p> is empty, we should replace it with a list element
4158 // and/or a list item element.
4159 if (HTMLEditUtils::IsEmptyNode(aHandlingDivOrParagraphElement,
4160 {EmptyCheckOption::TreatListItemAsVisible,
4161 EmptyCheckOption::TreatTableCellAsVisible})) {
4162 if (!aState.mCurrentListElement) {
4163 nsresult rv = CreateAndUpdateCurrentListElement(
4164 aHTMLEditor, EditorDOMPoint(&aHandlingDivOrParagraphElement),
4165 EmptyListItem::Create, aState, aEditingHost);
4166 if (NS_FAILED(rv)) {
4167 NS_WARNING(
4168 "AutoListElementCreator::CreateAndUpdateCurrentListElement("
4169 "EmptyListItem::Create) failed");
4170 return rv;
4172 } else {
4173 Result<CreateElementResult, nsresult> createListItemElementResult =
4174 AppendListItemElement(
4175 aHTMLEditor, MOZ_KnownLive(*aState.mCurrentListElement), aState);
4176 if (MOZ_UNLIKELY(createListItemElementResult.isErr())) {
4177 NS_WARNING("AutoListElementCreator::AppendListItemElement() failed");
4178 return createListItemElementResult.unwrapErr();
4180 CreateElementResult unwrappedResult =
4181 createListItemElementResult.unwrap();
4182 unwrappedResult.IgnoreCaretPointSuggestion();
4183 aState.mListOrListItemElementToPutCaret = unwrappedResult.UnwrapNewNode();
4185 nsresult rv =
4186 aHTMLEditor.DeleteNodeWithTransaction(aHandlingDivOrParagraphElement);
4187 if (NS_FAILED(rv)) {
4188 NS_WARNING("HTMLEditor::DeleteNodeWithTransaction() failed");
4189 return rv;
4192 // We don't want new inline contents inserted into the new list item element
4193 // because we want to keep the line break at end of
4194 // aHandlingDivOrParagraphElement.
4195 aState.mPreviousListItemElement = nullptr;
4197 return NS_OK;
4200 // If current node is a <div> element, replace it with its children and handle
4201 // them as same as topmost children in the range.
4202 AutoContentNodeArray arrayOfContentsInDiv;
4203 HTMLEditUtils::CollectChildren(aHandlingDivOrParagraphElement,
4204 arrayOfContentsInDiv, 0,
4205 {CollectChildrenOption::CollectListChildren,
4206 CollectChildrenOption::CollectTableChildren});
4208 Result<EditorDOMPoint, nsresult> unwrapDivElementResult =
4209 aHTMLEditor.RemoveContainerWithTransaction(
4210 aHandlingDivOrParagraphElement);
4211 if (MOZ_UNLIKELY(unwrapDivElementResult.isErr())) {
4212 NS_WARNING("HTMLEditor::RemoveContainerWithTransaction() failed");
4213 return unwrapDivElementResult.unwrapErr();
4216 for (const OwningNonNull<nsIContent>& content : arrayOfContentsInDiv) {
4217 // MOZ_KnownLive because of bug 1620312
4218 nsresult rv = HandleChildContent(aHTMLEditor, MOZ_KnownLive(content),
4219 aState, aEditingHost);
4220 if (NS_FAILED(rv)) {
4221 NS_WARNING("AutoListElementCreator::HandleChildContent() failed");
4222 return rv;
4226 // We don't want new inline contents inserted into the new list item element
4227 // because we want to keep the line break at end of
4228 // aHandlingDivOrParagraphElement.
4229 aState.mPreviousListItemElement = nullptr;
4231 return NS_OK;
4234 nsresult HTMLEditor::AutoListElementCreator::CreateAndUpdateCurrentListElement(
4235 HTMLEditor& aHTMLEditor, const EditorDOMPoint& aPointToInsert,
4236 EmptyListItem aEmptyListItem, AutoHandlingState& aState,
4237 const Element& aEditingHost) const {
4238 MOZ_ASSERT(aPointToInsert.IsSetAndValid());
4240 aState.mPreviousListItemElement = nullptr;
4241 RefPtr<Element> newListItemElement;
4242 auto initializer =
4243 // MOZ_CAN_RUN_SCRIPT_BOUNDARY due to bug 1758868
4244 [&](HTMLEditor&, Element& aListElement, const EditorDOMPoint&)
4245 MOZ_CAN_RUN_SCRIPT_BOUNDARY {
4246 // If the replacing element has `dir` attribute, the new list
4247 // element should take it to correct its list marker position.
4248 if (aState.mReplacingBlockElement) {
4249 nsString dirValue;
4250 if (aState.mReplacingBlockElement->GetAttr(nsGkAtoms::dir,
4251 dirValue) &&
4252 !dirValue.IsEmpty()) {
4253 // We don't need to use transaction to set `dir` attribute here
4254 // because the element will be stored with the `dir` attribute
4255 // in InsertNodeTransaction. Therefore, undo should work.
4256 IgnoredErrorResult ignoredError;
4257 aListElement.SetAttr(nsGkAtoms::dir, dirValue, ignoredError);
4258 NS_WARNING_ASSERTION(
4259 !ignoredError.Failed(),
4260 "Element::SetAttr(nsGkAtoms::dir) failed, but ignored");
4263 if (aEmptyListItem == EmptyListItem::Create) {
4264 Result<CreateElementResult, nsresult> createNewListItemResult =
4265 AppendListItemElement(aHTMLEditor, aListElement, aState);
4266 if (MOZ_UNLIKELY(createNewListItemResult.isErr())) {
4267 NS_WARNING(
4268 "HTMLEditor::AppendNewElementToInsertingElement()"
4269 " failed");
4270 return createNewListItemResult.unwrapErr();
4272 CreateElementResult unwrappedResult =
4273 createNewListItemResult.unwrap();
4274 unwrappedResult.IgnoreCaretPointSuggestion();
4275 newListItemElement = unwrappedResult.UnwrapNewNode();
4277 return NS_OK;
4279 Result<CreateElementResult, nsresult> createNewListElementResult =
4280 aHTMLEditor.InsertElementWithSplittingAncestorsWithTransaction(
4281 mListTagName, aPointToInsert, BRElementNextToSplitPoint::Keep,
4282 aEditingHost, initializer);
4283 if (MOZ_UNLIKELY(createNewListElementResult.isErr())) {
4284 NS_WARNING(
4285 nsPrintfCString(
4286 "HTMLEditor::"
4287 "InsertElementWithSplittingAncestorsWithTransaction(%s) failed",
4288 nsAtomCString(&mListTagName).get())
4289 .get());
4290 return createNewListElementResult.propagateErr();
4292 CreateElementResult unwrappedCreateNewListElementResult =
4293 createNewListElementResult.unwrap();
4294 unwrappedCreateNewListElementResult.IgnoreCaretPointSuggestion();
4296 MOZ_ASSERT(unwrappedCreateNewListElementResult.GetNewNode());
4297 aState.mListOrListItemElementToPutCaret =
4298 newListItemElement ? newListItemElement.get()
4299 : unwrappedCreateNewListElementResult.GetNewNode();
4300 aState.mCurrentListElement =
4301 unwrappedCreateNewListElementResult.UnwrapNewNode();
4302 aState.mPreviousListItemElement = std::move(newListItemElement);
4303 return NS_OK;
4306 // static
4307 nsresult HTMLEditor::AutoListElementCreator::MaybeCloneAttributesToNewListItem(
4308 HTMLEditor& aHTMLEditor, Element& aListItemElement,
4309 AutoHandlingState& aState) {
4310 if (!aState.mReplacingBlockElement) {
4311 return NS_OK;
4313 // If we're replacing a block element, the list items should have attributes
4314 // of the replacing element. However, we don't want to copy `dir` attribute
4315 // because it does not affect content in list item element and setting
4316 // opposite direction from the parent list causes the marker invisible.
4317 // Therefore, we don't want to take it. Finally, we don't need to use
4318 // transaction to copy the attributes here because the element will be stored
4319 // with the attributes in InsertNodeTransaction. Therefore, undo should work.
4320 nsresult rv = aHTMLEditor.CopyAttributes(
4321 WithTransaction::No, aListItemElement,
4322 MOZ_KnownLive(*aState.mReplacingBlockElement),
4323 aState.mMaybeCopiedReplacingBlockElementId
4324 ? HTMLEditor::CopyAllAttributesExceptIdAndDir
4325 : HTMLEditor::CopyAllAttributesExceptDir);
4326 aState.mMaybeCopiedReplacingBlockElementId = true;
4327 if (NS_WARN_IF(aHTMLEditor.Destroyed())) {
4328 return NS_ERROR_EDITOR_DESTROYED;
4330 NS_WARNING_ASSERTION(
4331 NS_SUCCEEDED(rv),
4332 "HTMLEditor::CopyAttributes(WithTransaction::No) failed");
4333 return rv;
4336 Result<CreateElementResult, nsresult>
4337 HTMLEditor::AutoListElementCreator::AppendListItemElement(
4338 HTMLEditor& aHTMLEditor, const Element& aListElement,
4339 AutoHandlingState& aState) const {
4340 const WithTransaction withTransaction = aListElement.IsInComposedDoc()
4341 ? WithTransaction::Yes
4342 : WithTransaction::No;
4343 Result<CreateElementResult, nsresult> createNewListItemResult =
4344 aHTMLEditor.CreateAndInsertElement(
4345 withTransaction, mListItemTagName,
4346 EditorDOMPoint::AtEndOf(aListElement),
4347 !aState.mReplacingBlockElement
4348 ? HTMLEditor::DoNothingForNewElement
4349 // MOZ_CAN_RUN_SCRIPT_BOUNDARY due to bug 1758868
4350 : [&aState](HTMLEditor& aHTMLEditor, Element& aListItemElement,
4351 const EditorDOMPoint&) MOZ_CAN_RUN_SCRIPT_BOUNDARY {
4352 nsresult rv =
4353 AutoListElementCreator::MaybeCloneAttributesToNewListItem(
4354 aHTMLEditor, aListItemElement, aState);
4355 NS_WARNING_ASSERTION(
4356 NS_SUCCEEDED(rv),
4357 "AutoListElementCreator::"
4358 "MaybeCloneAttributesToNewListItem() failed");
4359 return rv;
4361 NS_WARNING_ASSERTION(createNewListItemResult.isOk(),
4362 "HTMLEditor::CreateAndInsertElement() failed");
4363 return createNewListItemResult;
4366 nsresult HTMLEditor::AutoListElementCreator::HandleChildInlineContent(
4367 HTMLEditor& aHTMLEditor, nsIContent& aHandlingInlineContent,
4368 AutoHandlingState& aState) const {
4369 MOZ_ASSERT(HTMLEditUtils::IsInlineContent(
4370 aHandlingInlineContent, BlockInlineCheck::UseHTMLDefaultStyle));
4372 // If we're currently handling contents of a list item and current node
4373 // is not a block element, move current node into the list item.
4374 if (!aState.mPreviousListItemElement) {
4375 nsresult rv = WrapContentIntoNewListItemElement(
4376 aHTMLEditor, aHandlingInlineContent, aState);
4377 NS_WARNING_ASSERTION(
4378 NS_SUCCEEDED(rv),
4379 "AutoListElementCreator::WrapContentIntoNewListItemElement() failed");
4380 return rv;
4383 Result<MoveNodeResult, nsresult> moveInlineElementResult =
4384 aHTMLEditor.MoveNodeToEndWithTransaction(
4385 aHandlingInlineContent,
4386 MOZ_KnownLive(*aState.mPreviousListItemElement));
4387 if (MOZ_UNLIKELY(moveInlineElementResult.isErr())) {
4388 NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed");
4389 return moveInlineElementResult.propagateErr();
4391 moveInlineElementResult.inspect().IgnoreCaretPointSuggestion();
4392 return NS_OK;
4395 nsresult HTMLEditor::AutoListElementCreator::WrapContentIntoNewListItemElement(
4396 HTMLEditor& aHTMLEditor, nsIContent& aHandlingContent,
4397 AutoHandlingState& aState) const {
4398 // If current node is not a paragraph, wrap current node with new list
4399 // item element and move it into current list element.
4400 Result<CreateElementResult, nsresult> wrapContentInListItemElementResult =
4401 aHTMLEditor.InsertContainerWithTransaction(
4402 aHandlingContent, mListItemTagName,
4403 !aState.mReplacingBlockElement
4404 ? HTMLEditor::DoNothingForNewElement
4405 // MOZ_CAN_RUN_SCRIPT_BOUNDARY due to bug 1758868
4406 : [&aState](HTMLEditor& aHTMLEditor, Element& aListItemElement,
4407 const EditorDOMPoint&) MOZ_CAN_RUN_SCRIPT_BOUNDARY {
4408 nsresult rv =
4409 AutoListElementCreator::MaybeCloneAttributesToNewListItem(
4410 aHTMLEditor, aListItemElement, aState);
4411 NS_WARNING_ASSERTION(
4412 NS_SUCCEEDED(rv),
4413 "AutoListElementCreator::"
4414 "MaybeCloneAttributesToNewListItem() failed");
4415 return rv;
4417 if (MOZ_UNLIKELY(wrapContentInListItemElementResult.isErr())) {
4418 NS_WARNING("HTMLEditor::InsertContainerWithTransaction() failed");
4419 return wrapContentInListItemElementResult.unwrapErr();
4421 CreateElementResult unwrappedWrapContentInListItemElementResult =
4422 wrapContentInListItemElementResult.unwrap();
4423 unwrappedWrapContentInListItemElementResult.IgnoreCaretPointSuggestion();
4424 MOZ_ASSERT(unwrappedWrapContentInListItemElementResult.GetNewNode());
4426 // MOZ_KnownLive(unwrappedWrapContentInListItemElementResult.GetNewNode()):
4427 // The result is grabbed by unwrappedWrapContentInListItemElementResult.
4428 Result<MoveNodeResult, nsresult> moveListItemElementResult =
4429 aHTMLEditor.MoveNodeToEndWithTransaction(
4430 MOZ_KnownLive(
4431 *unwrappedWrapContentInListItemElementResult.GetNewNode()),
4432 MOZ_KnownLive(*aState.mCurrentListElement));
4433 if (MOZ_UNLIKELY(moveListItemElementResult.isErr())) {
4434 NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed");
4435 return moveListItemElementResult.unwrapErr();
4437 moveListItemElementResult.inspect().IgnoreCaretPointSuggestion();
4439 // If current node is not a block element, new list item should have
4440 // following inline nodes too.
4441 if (HTMLEditUtils::IsInlineContent(aHandlingContent,
4442 BlockInlineCheck::UseHTMLDefaultStyle)) {
4443 aState.mPreviousListItemElement =
4444 unwrappedWrapContentInListItemElementResult.UnwrapNewNode();
4445 } else {
4446 aState.mPreviousListItemElement = nullptr;
4449 // XXX Why don't we set `type` attribute here??
4450 return NS_OK;
4453 nsresult HTMLEditor::AutoListElementCreator::
4454 EnsureCollapsedRangeIsInListItemOrListElement(
4455 Element& aListItemOrListToPutCaret, AutoRangeArray& aRanges) const {
4456 if (!aRanges.IsCollapsed() || aRanges.Ranges().IsEmpty()) {
4457 return NS_OK;
4460 const auto firstRangeStartPoint =
4461 aRanges.GetFirstRangeStartPoint<EditorRawDOMPoint>();
4462 if (MOZ_UNLIKELY(!firstRangeStartPoint.IsSet())) {
4463 return NS_OK;
4465 Result<EditorRawDOMPoint, nsresult> pointToPutCaretOrError =
4466 HTMLEditUtils::ComputePointToPutCaretInElementIfOutside<
4467 EditorRawDOMPoint>(aListItemOrListToPutCaret, firstRangeStartPoint);
4468 if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) {
4469 NS_WARNING("HTMLEditUtils::ComputePointToPutCaretInElementIfOutside()");
4470 return pointToPutCaretOrError.unwrapErr();
4472 if (pointToPutCaretOrError.inspect().IsSet()) {
4473 nsresult rv = aRanges.Collapse(pointToPutCaretOrError.inspect());
4474 if (NS_FAILED(rv)) {
4475 NS_WARNING("AutoRangeArray::Collapse() failed");
4476 return rv;
4479 return NS_OK;
4482 nsresult HTMLEditor::RemoveListAtSelectionAsSubAction(
4483 const Element& aEditingHost) {
4484 MOZ_ASSERT(IsEditActionDataAvailable());
4487 Result<EditActionResult, nsresult> result = CanHandleHTMLEditSubAction();
4488 if (MOZ_UNLIKELY(result.isErr())) {
4489 NS_WARNING("HTMLEditor::CanHandleHTMLEditSubAction() failed");
4490 return result.unwrapErr();
4492 if (result.inspect().Canceled()) {
4493 return NS_OK;
4497 AutoPlaceholderBatch treatAsOneTransaction(
4498 *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
4499 IgnoredErrorResult error;
4500 AutoEditSubActionNotifier startToHandleEditSubAction(
4501 *this, EditSubAction::eRemoveList, nsIEditor::eNext, error);
4502 if (NS_WARN_IF(error.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
4503 return error.StealNSResult();
4505 NS_WARNING_ASSERTION(
4506 !error.Failed(),
4507 "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
4509 // XXX Why do we do this only when there is only one selection range?
4510 if (!SelectionRef().IsCollapsed() && SelectionRef().RangeCount() == 1u) {
4511 Result<EditorRawDOMRange, nsresult> extendedRange =
4512 GetRangeExtendedToHardLineEdgesForBlockEditAction(
4513 SelectionRef().GetRangeAt(0u), aEditingHost);
4514 if (MOZ_UNLIKELY(extendedRange.isErr())) {
4515 NS_WARNING(
4516 "HTMLEditor::GetRangeExtendedToHardLineEdgesForBlockEditAction() "
4517 "failed");
4518 return extendedRange.unwrapErr();
4520 // Note that end point may be prior to start point. So, we
4521 // cannot use Selection::SetStartAndEndInLimit() here.
4522 error.SuppressException();
4523 SelectionRef().SetBaseAndExtentInLimiter(
4524 extendedRange.inspect().StartRef().ToRawRangeBoundary(),
4525 extendedRange.inspect().EndRef().ToRawRangeBoundary(), error);
4526 if (NS_WARN_IF(Destroyed())) {
4527 return NS_ERROR_EDITOR_DESTROYED;
4529 if (error.Failed()) {
4530 NS_WARNING("Selection::SetBaseAndExtentInLimiter() failed");
4531 return error.StealNSResult();
4535 AutoSelectionRestorer restoreSelectionLater(*this);
4537 AutoTArray<OwningNonNull<nsIContent>, 64> arrayOfContents;
4539 // TODO: We don't need AutoTransactionsConserveSelection here in the normal
4540 // cases, but removing this may cause the behavior with the legacy
4541 // mutation event listeners. We should try to delete this in a bug.
4542 AutoTransactionsConserveSelection dontChangeMySelection(*this);
4545 AutoRangeArray extendedSelectionRanges(SelectionRef());
4546 extendedSelectionRanges.ExtendRangesToWrapLines(
4547 EditSubAction::eCreateOrChangeList,
4548 BlockInlineCheck::UseHTMLDefaultStyle, aEditingHost);
4549 Result<EditorDOMPoint, nsresult> splitResult =
4550 extendedSelectionRanges
4551 .SplitTextAtEndBoundariesAndInlineAncestorsAtBothBoundaries(
4552 *this, BlockInlineCheck::UseHTMLDefaultStyle, aEditingHost);
4553 if (MOZ_UNLIKELY(splitResult.isErr())) {
4554 NS_WARNING(
4555 "AutoRangeArray::"
4556 "SplitTextAtEndBoundariesAndInlineAncestorsAtBothBoundaries() "
4557 "failed");
4558 return splitResult.unwrapErr();
4560 nsresult rv = extendedSelectionRanges.CollectEditTargetNodes(
4561 *this, arrayOfContents, EditSubAction::eCreateOrChangeList,
4562 AutoRangeArray::CollectNonEditableNodes::No);
4563 if (NS_FAILED(rv)) {
4564 NS_WARNING(
4565 "AutoRangeArray::CollectEditTargetNodes(EditSubAction::"
4566 "eCreateOrChangeList, CollectNonEditableNodes::No) failed");
4567 return rv;
4571 const Result<EditorDOMPoint, nsresult> splitAtBRElementsResult =
4572 MaybeSplitElementsAtEveryBRElement(arrayOfContents,
4573 EditSubAction::eCreateOrChangeList);
4574 if (MOZ_UNLIKELY(splitAtBRElementsResult.isErr())) {
4575 NS_WARNING(
4576 "HTMLEditor::MaybeSplitElementsAtEveryBRElement(EditSubAction::"
4577 "eCreateOrChangeList) failed");
4578 return splitAtBRElementsResult.inspectErr();
4582 // Remove all non-editable nodes. Leave them be.
4583 // XXX CollectEditTargetNodes() should return only editable contents when it's
4584 // called with CollectNonEditableNodes::No, but checking it here, looks
4585 // like just wasting the runtime cost.
4586 for (int32_t i = arrayOfContents.Length() - 1; i >= 0; i--) {
4587 OwningNonNull<nsIContent>& content = arrayOfContents[i];
4588 if (!EditorUtils::IsEditableContent(content, EditorType::HTML)) {
4589 arrayOfContents.RemoveElementAt(i);
4593 // Only act on lists or list items in the array
4594 for (auto& content : arrayOfContents) {
4595 // here's where we actually figure out what to do
4596 if (HTMLEditUtils::IsListItem(content)) {
4597 // unlist this listitem
4598 nsresult rv = LiftUpListItemElement(MOZ_KnownLive(*content->AsElement()),
4599 LiftUpFromAllParentListElements::Yes);
4600 if (NS_FAILED(rv)) {
4601 NS_WARNING(
4602 "HTMLEditor::LiftUpListItemElement(LiftUpFromAllParentListElements:"
4603 ":Yes) failed");
4604 return rv;
4606 continue;
4608 if (HTMLEditUtils::IsAnyListElement(content)) {
4609 // node is a list, move list items out
4610 nsresult rv =
4611 DestroyListStructureRecursively(MOZ_KnownLive(*content->AsElement()));
4612 if (NS_FAILED(rv)) {
4613 NS_WARNING("HTMLEditor::DestroyListStructureRecursively() failed");
4614 return rv;
4616 continue;
4619 return NS_OK;
4622 Result<RefPtr<Element>, nsresult>
4623 HTMLEditor::FormatBlockContainerWithTransaction(
4624 AutoRangeArray& aSelectionRanges, const nsStaticAtom& aNewFormatTagName,
4625 FormatBlockMode aFormatBlockMode, const Element& aEditingHost) {
4626 MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable());
4628 // XXX Why do we do this only when there is only one selection range?
4629 if (!aSelectionRanges.IsCollapsed() &&
4630 aSelectionRanges.Ranges().Length() == 1u) {
4631 Result<EditorRawDOMRange, nsresult> extendedRange =
4632 GetRangeExtendedToHardLineEdgesForBlockEditAction(
4633 aSelectionRanges.FirstRangeRef(), aEditingHost);
4634 if (MOZ_UNLIKELY(extendedRange.isErr())) {
4635 NS_WARNING(
4636 "HTMLEditor::GetRangeExtendedToHardLineEdgesForBlockEditAction() "
4637 "failed");
4638 return extendedRange.propagateErr();
4640 // Note that end point may be prior to start point. So, we
4641 // cannot use AutoRangeArray::SetStartAndEnd() here.
4642 if (NS_FAILED(aSelectionRanges.SetBaseAndExtent(
4643 extendedRange.inspect().StartRef(),
4644 extendedRange.inspect().EndRef()))) {
4645 NS_WARNING("AutoRangeArray::SetBaseAndExtent() failed");
4646 return Err(NS_ERROR_FAILURE);
4650 MOZ_ALWAYS_TRUE(aSelectionRanges.SaveAndTrackRanges(*this));
4652 // TODO: We don't need AutoTransactionsConserveSelection here in the normal
4653 // cases, but removing this may cause the behavior with the legacy
4654 // mutation event listeners. We should try to delete this in a bug.
4655 AutoTransactionsConserveSelection dontChangeMySelection(*this);
4657 AutoTArray<OwningNonNull<nsIContent>, 64> arrayOfContents;
4658 aSelectionRanges.ExtendRangesToWrapLines(
4659 aFormatBlockMode == FormatBlockMode::HTMLFormatBlockCommand
4660 ? EditSubAction::eFormatBlockForHTMLCommand
4661 : EditSubAction::eCreateOrRemoveBlock,
4662 BlockInlineCheck::UseComputedDisplayOutsideStyle, aEditingHost);
4663 Result<EditorDOMPoint, nsresult> splitResult =
4664 aSelectionRanges
4665 .SplitTextAtEndBoundariesAndInlineAncestorsAtBothBoundaries(
4666 *this, BlockInlineCheck::UseComputedDisplayOutsideStyle,
4667 aEditingHost);
4668 if (MOZ_UNLIKELY(splitResult.isErr())) {
4669 NS_WARNING(
4670 "AutoRangeArray::"
4671 "SplitTextAtEndBoundariesAndInlineAncestorsAtBothBoundaries() failed");
4672 return splitResult.propagateErr();
4674 nsresult rv = aSelectionRanges.CollectEditTargetNodes(
4675 *this, arrayOfContents,
4676 aFormatBlockMode == FormatBlockMode::HTMLFormatBlockCommand
4677 ? EditSubAction::eFormatBlockForHTMLCommand
4678 : EditSubAction::eCreateOrRemoveBlock,
4679 AutoRangeArray::CollectNonEditableNodes::Yes);
4680 if (NS_FAILED(rv)) {
4681 NS_WARNING(
4682 "AutoRangeArray::CollectEditTargetNodes(CollectNonEditableNodes::No) "
4683 "failed");
4684 return Err(rv);
4687 Result<EditorDOMPoint, nsresult> splitAtBRElementsResult =
4688 MaybeSplitElementsAtEveryBRElement(
4689 arrayOfContents,
4690 aFormatBlockMode == FormatBlockMode::HTMLFormatBlockCommand
4691 ? EditSubAction::eFormatBlockForHTMLCommand
4692 : EditSubAction::eCreateOrRemoveBlock);
4693 if (MOZ_UNLIKELY(splitAtBRElementsResult.isErr())) {
4694 NS_WARNING("HTMLEditor::MaybeSplitElementsAtEveryBRElement() failed");
4695 return splitAtBRElementsResult.propagateErr();
4698 // If there is no visible and editable nodes in the edit targets, make an
4699 // empty block.
4700 // XXX Isn't this odd if there are only non-editable visible nodes?
4701 if (HTMLEditUtils::IsEmptyOneHardLine(
4702 arrayOfContents, BlockInlineCheck::UseComputedDisplayOutsideStyle)) {
4703 if (NS_WARN_IF(aSelectionRanges.Ranges().IsEmpty())) {
4704 return Err(NS_ERROR_FAILURE);
4707 auto pointToInsertBlock =
4708 aSelectionRanges.GetFirstRangeStartPoint<EditorDOMPoint>();
4709 if (aFormatBlockMode == FormatBlockMode::XULParagraphStateCommand &&
4710 (&aNewFormatTagName == nsGkAtoms::normal ||
4711 &aNewFormatTagName == nsGkAtoms::_empty)) {
4712 if (!pointToInsertBlock.IsInContentNode()) {
4713 NS_WARNING(
4714 "HTMLEditor::FormatBlockContainerWithTransaction() couldn't find "
4715 "block parent because container of the point is not content");
4716 return Err(NS_ERROR_FAILURE);
4718 // We are removing blocks (going to "body text")
4719 const RefPtr<Element> editableBlockElement =
4720 HTMLEditUtils::GetInclusiveAncestorElement(
4721 *pointToInsertBlock.ContainerAs<nsIContent>(),
4722 HTMLEditUtils::ClosestEditableBlockElement,
4723 BlockInlineCheck::UseComputedDisplayOutsideStyle);
4724 if (!editableBlockElement) {
4725 NS_WARNING(
4726 "HTMLEditor::FormatBlockContainerWithTransaction() couldn't find "
4727 "block parent");
4728 return Err(NS_ERROR_FAILURE);
4730 if (editableBlockElement->IsAnyOfHTMLElements(
4731 nsGkAtoms::dd, nsGkAtoms::dl, nsGkAtoms::dt) ||
4732 !HTMLEditUtils::IsFormatElementForParagraphStateCommand(
4733 *editableBlockElement)) {
4734 return RefPtr<Element>();
4737 // If the first editable node after selection is a br, consume it.
4738 // Otherwise it gets pushed into a following block after the split,
4739 // which is visually bad.
4740 if (nsCOMPtr<nsIContent> brContent = HTMLEditUtils::GetNextContent(
4741 pointToInsertBlock, {WalkTreeOption::IgnoreNonEditableNode},
4742 BlockInlineCheck::UseComputedDisplayOutsideStyle,
4743 &aEditingHost)) {
4744 if (brContent && brContent->IsHTMLElement(nsGkAtoms::br)) {
4745 AutoEditorDOMPointChildInvalidator lockOffset(pointToInsertBlock);
4746 nsresult rv = DeleteNodeWithTransaction(*brContent);
4747 if (NS_FAILED(rv)) {
4748 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
4749 return Err(rv);
4753 // Do the splits!
4754 Result<SplitNodeResult, nsresult> splitNodeResult =
4755 SplitNodeDeepWithTransaction(
4756 *editableBlockElement, pointToInsertBlock,
4757 SplitAtEdges::eDoNotCreateEmptyContainer);
4758 if (MOZ_UNLIKELY(splitNodeResult.isErr())) {
4759 NS_WARNING("HTMLEditor::SplitNodeDeepWithTransaction() failed");
4760 return splitNodeResult.propagateErr();
4762 SplitNodeResult unwrappedSplitNodeResult = splitNodeResult.unwrap();
4763 unwrappedSplitNodeResult.IgnoreCaretPointSuggestion();
4764 // Put a <br> element at the split point
4765 Result<CreateElementResult, nsresult> insertBRElementResult =
4766 InsertBRElement(
4767 WithTransaction::Yes,
4768 unwrappedSplitNodeResult.AtSplitPoint<EditorDOMPoint>());
4769 if (MOZ_UNLIKELY(insertBRElementResult.isErr())) {
4770 NS_WARNING("HTMLEditor::InsertBRElement(WithTransaction::Yes) failed");
4771 return insertBRElementResult.propagateErr();
4773 MOZ_ASSERT(insertBRElementResult.inspect().GetNewNode());
4774 aSelectionRanges.ClearSavedRanges();
4775 nsresult rv = aSelectionRanges.Collapse(
4776 EditorRawDOMPoint(insertBRElementResult.inspect().GetNewNode()));
4777 if (NS_FAILED(rv)) {
4778 NS_WARNING("AutoRangeArray::Collapse() failed");
4779 return Err(rv);
4781 return RefPtr<Element>();
4784 // We are making a block. Consume a br, if needed.
4785 if (nsCOMPtr<nsIContent> maybeBRContent = HTMLEditUtils::GetNextContent(
4786 pointToInsertBlock,
4787 {WalkTreeOption::IgnoreNonEditableNode,
4788 WalkTreeOption::StopAtBlockBoundary},
4789 BlockInlineCheck::UseComputedDisplayOutsideStyle, &aEditingHost)) {
4790 if (maybeBRContent->IsHTMLElement(nsGkAtoms::br)) {
4791 AutoEditorDOMPointChildInvalidator lockOffset(pointToInsertBlock);
4792 nsresult rv = DeleteNodeWithTransaction(*maybeBRContent);
4793 if (NS_FAILED(rv)) {
4794 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
4795 return Err(rv);
4797 // We don't need to act on this node any more
4798 arrayOfContents.RemoveElement(maybeBRContent);
4801 // Make sure we can put a block here.
4802 Result<CreateElementResult, nsresult> createNewBlockElementResult =
4803 InsertElementWithSplittingAncestorsWithTransaction(
4804 aNewFormatTagName, pointToInsertBlock,
4805 BRElementNextToSplitPoint::Keep, aEditingHost);
4806 if (MOZ_UNLIKELY(createNewBlockElementResult.isErr())) {
4807 NS_WARNING(
4808 nsPrintfCString(
4809 "HTMLEditor::InsertElementWithSplittingAncestorsWithTransaction("
4810 "%s) failed",
4811 nsAtomCString(&aNewFormatTagName).get())
4812 .get());
4813 return createNewBlockElementResult.propagateErr();
4815 CreateElementResult unwrappedCreateNewBlockElementResult =
4816 createNewBlockElementResult.unwrap();
4817 unwrappedCreateNewBlockElementResult.IgnoreCaretPointSuggestion();
4818 MOZ_ASSERT(unwrappedCreateNewBlockElementResult.GetNewNode());
4820 // Delete anything that was in the list of nodes
4821 while (!arrayOfContents.IsEmpty()) {
4822 OwningNonNull<nsIContent>& content = arrayOfContents[0];
4823 // MOZ_KnownLive because 'arrayOfContents' is guaranteed to
4824 // keep it alive.
4825 nsresult rv = DeleteNodeWithTransaction(MOZ_KnownLive(*content));
4826 if (NS_FAILED(rv)) {
4827 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
4828 return Err(rv);
4830 arrayOfContents.RemoveElementAt(0);
4832 // Put selection in new block
4833 aSelectionRanges.ClearSavedRanges();
4834 nsresult rv = aSelectionRanges.Collapse(EditorRawDOMPoint(
4835 unwrappedCreateNewBlockElementResult.GetNewNode(), 0u));
4836 if (NS_FAILED(rv)) {
4837 NS_WARNING("AutoRangeArray::Collapse() failed");
4838 return Err(rv);
4840 return unwrappedCreateNewBlockElementResult.UnwrapNewNode();
4843 if (aFormatBlockMode == FormatBlockMode::XULParagraphStateCommand) {
4844 // Okay, now go through all the nodes and make the right kind of blocks, or
4845 // whatever is appropriate.
4846 // Note: blockquote is handled a little differently.
4847 if (&aNewFormatTagName == nsGkAtoms::blockquote) {
4848 Result<CreateElementResult, nsresult>
4849 wrapContentsInBlockquoteElementsResult =
4850 WrapContentsInBlockquoteElementsWithTransaction(arrayOfContents,
4851 aEditingHost);
4852 if (MOZ_UNLIKELY(wrapContentsInBlockquoteElementsResult.isErr())) {
4853 NS_WARNING(
4854 "HTMLEditor::WrapContentsInBlockquoteElementsWithTransaction() "
4855 "failed");
4856 return wrapContentsInBlockquoteElementsResult.propagateErr();
4858 wrapContentsInBlockquoteElementsResult.inspect()
4859 .IgnoreCaretPointSuggestion();
4860 return wrapContentsInBlockquoteElementsResult.unwrap().UnwrapNewNode();
4862 if (&aNewFormatTagName == nsGkAtoms::normal ||
4863 &aNewFormatTagName == nsGkAtoms::_empty) {
4864 Result<EditorDOMPoint, nsresult> removeBlockContainerElementsResult =
4865 RemoveBlockContainerElementsWithTransaction(
4866 arrayOfContents, FormatBlockMode::XULParagraphStateCommand,
4867 BlockInlineCheck::UseComputedDisplayOutsideStyle);
4868 if (MOZ_UNLIKELY(removeBlockContainerElementsResult.isErr())) {
4869 NS_WARNING(
4870 "HTMLEditor::RemoveBlockContainerElementsWithTransaction() failed");
4871 return removeBlockContainerElementsResult.propagateErr();
4873 return RefPtr<Element>();
4877 Result<CreateElementResult, nsresult> wrapContentsInBlockElementResult =
4878 CreateOrChangeFormatContainerElement(arrayOfContents, aNewFormatTagName,
4879 aFormatBlockMode, aEditingHost);
4880 if (MOZ_UNLIKELY(wrapContentsInBlockElementResult.isErr())) {
4881 NS_WARNING("HTMLEditor::CreateOrChangeFormatContainerElement() failed");
4882 return wrapContentsInBlockElementResult.propagateErr();
4884 wrapContentsInBlockElementResult.inspect().IgnoreCaretPointSuggestion();
4885 return wrapContentsInBlockElementResult.unwrap().UnwrapNewNode();
4888 nsresult HTMLEditor::MaybeInsertPaddingBRElementForEmptyLastLineAtSelection() {
4889 MOZ_ASSERT(IsEditActionDataAvailable());
4890 MOZ_ASSERT(!IsSelectionRangeContainerNotContent());
4892 if (!SelectionRef().IsCollapsed()) {
4893 return NS_OK;
4896 const nsRange* firstRange = SelectionRef().GetRangeAt(0);
4897 if (NS_WARN_IF(!firstRange)) {
4898 return NS_ERROR_FAILURE;
4900 const RangeBoundary& atStartOfSelection = firstRange->StartRef();
4901 if (NS_WARN_IF(!atStartOfSelection.IsSet())) {
4902 return NS_ERROR_FAILURE;
4904 if (!atStartOfSelection.Container()->IsElement()) {
4905 return NS_OK;
4907 OwningNonNull<Element> startContainerElement =
4908 *atStartOfSelection.Container()->AsElement();
4909 nsresult rv =
4910 InsertPaddingBRElementForEmptyLastLineIfNeeded(startContainerElement);
4911 NS_WARNING_ASSERTION(
4912 NS_SUCCEEDED(rv),
4913 "HTMLEditor::InsertPaddingBRElementForEmptyLastLineIfNeeded() failed");
4914 return rv;
4917 Result<EditActionResult, nsresult> HTMLEditor::IndentAsSubAction(
4918 const Element& aEditingHost) {
4919 MOZ_ASSERT(IsEditActionDataAvailable());
4921 AutoPlaceholderBatch treatAsOneTransaction(
4922 *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
4923 IgnoredErrorResult ignoredError;
4924 AutoEditSubActionNotifier startToHandleEditSubAction(
4925 *this, EditSubAction::eIndent, nsIEditor::eNext, ignoredError);
4926 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
4927 return Err(ignoredError.StealNSResult());
4929 NS_WARNING_ASSERTION(
4930 !ignoredError.Failed(),
4931 "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
4934 Result<EditActionResult, nsresult> result = CanHandleHTMLEditSubAction();
4935 if (MOZ_UNLIKELY(result.isErr())) {
4936 NS_WARNING("HTMLEditor::CanHandleHTMLEditSubAction() failed");
4937 return result;
4939 if (result.inspect().Canceled()) {
4940 return result;
4944 if (MOZ_UNLIKELY(IsSelectionRangeContainerNotContent())) {
4945 NS_WARNING("Some selection containers are not content node, but ignored");
4946 return EditActionResult::IgnoredResult();
4949 Result<EditActionResult, nsresult> result =
4950 HandleIndentAtSelection(aEditingHost);
4951 if (MOZ_UNLIKELY(result.isErr())) {
4952 NS_WARNING("HTMLEditor::HandleIndentAtSelection() failed");
4953 return result;
4955 if (result.inspect().Canceled()) {
4956 return result;
4959 if (MOZ_UNLIKELY(IsSelectionRangeContainerNotContent())) {
4960 NS_WARNING("Mutation event listener might have changed selection");
4961 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
4964 // TODO: Investigate when we need to put a `<br>` element after indenting
4965 // ranges. Then, we could stop calling this here, or maybe we need to
4966 // do it while moving content nodes.
4967 nsresult rv = MaybeInsertPaddingBRElementForEmptyLastLineAtSelection();
4968 if (NS_FAILED(rv)) {
4969 NS_WARNING(
4970 "MaybeInsertPaddingBRElementForEmptyLastLineAtSelection() failed");
4971 return Err(rv);
4973 return result;
4976 Result<EditorDOMPoint, nsresult> HTMLEditor::IndentListChildWithTransaction(
4977 RefPtr<Element>* aSubListElement, const EditorDOMPoint& aPointInListElement,
4978 nsIContent& aContentMovingToSubList, const Element& aEditingHost) {
4979 MOZ_ASSERT(
4980 HTMLEditUtils::IsAnyListElement(aPointInListElement.GetContainer()),
4981 "unexpected container");
4982 MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable());
4984 // some logic for putting list items into nested lists...
4986 // If aContentMovingToSubList is followed by a sub-list element whose tag is
4987 // same as the parent list element's tag, we can move it to start of the
4988 // sub-list.
4989 if (nsIContent* nextEditableSibling = HTMLEditUtils::GetNextSibling(
4990 aContentMovingToSubList, {WalkTreeOption::IgnoreWhiteSpaceOnlyText,
4991 WalkTreeOption::IgnoreNonEditableNode})) {
4992 if (HTMLEditUtils::IsAnyListElement(nextEditableSibling) &&
4993 aPointInListElement.GetContainer()->NodeInfo()->NameAtom() ==
4994 nextEditableSibling->NodeInfo()->NameAtom() &&
4995 aPointInListElement.GetContainer()->NodeInfo()->NamespaceID() ==
4996 nextEditableSibling->NodeInfo()->NamespaceID()) {
4997 Result<MoveNodeResult, nsresult> moveListElementResult =
4998 MoveNodeWithTransaction(aContentMovingToSubList,
4999 EditorDOMPoint(nextEditableSibling, 0u));
5000 if (MOZ_UNLIKELY(moveListElementResult.isErr())) {
5001 NS_WARNING("HTMLEditor::MoveNodeWithTransaction() failed");
5002 return moveListElementResult.propagateErr();
5004 return moveListElementResult.unwrap().UnwrapCaretPoint();
5008 // If aContentMovingToSubList follows a sub-list element whose tag is same
5009 // as the parent list element's tag, we can move it to end of the sub-list.
5010 if (nsCOMPtr<nsIContent> previousEditableSibling =
5011 HTMLEditUtils::GetPreviousSibling(
5012 aContentMovingToSubList,
5013 {WalkTreeOption::IgnoreWhiteSpaceOnlyText,
5014 WalkTreeOption::IgnoreNonEditableNode})) {
5015 if (HTMLEditUtils::IsAnyListElement(previousEditableSibling) &&
5016 aPointInListElement.GetContainer()->NodeInfo()->NameAtom() ==
5017 previousEditableSibling->NodeInfo()->NameAtom() &&
5018 aPointInListElement.GetContainer()->NodeInfo()->NamespaceID() ==
5019 previousEditableSibling->NodeInfo()->NamespaceID()) {
5020 Result<MoveNodeResult, nsresult> moveListElementResult =
5021 MoveNodeToEndWithTransaction(aContentMovingToSubList,
5022 *previousEditableSibling);
5023 if (MOZ_UNLIKELY(moveListElementResult.isErr())) {
5024 NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed");
5025 return moveListElementResult.propagateErr();
5027 return moveListElementResult.unwrap().UnwrapCaretPoint();
5031 // If aContentMovingToSubList does not follow aSubListElement, we need
5032 // to create new sub-list element.
5033 EditorDOMPoint pointToPutCaret;
5034 nsIContent* previousEditableSibling =
5035 *aSubListElement ? HTMLEditUtils::GetPreviousSibling(
5036 aContentMovingToSubList,
5037 {WalkTreeOption::IgnoreWhiteSpaceOnlyText,
5038 WalkTreeOption::IgnoreNonEditableNode})
5039 : nullptr;
5040 if (!*aSubListElement || (previousEditableSibling &&
5041 previousEditableSibling != *aSubListElement)) {
5042 nsAtom* containerName =
5043 aPointInListElement.GetContainer()->NodeInfo()->NameAtom();
5044 // Create a new nested list of correct type.
5045 Result<CreateElementResult, nsresult> createNewListElementResult =
5046 InsertElementWithSplittingAncestorsWithTransaction(
5047 MOZ_KnownLive(*containerName), aPointInListElement,
5048 BRElementNextToSplitPoint::Keep, aEditingHost);
5049 if (MOZ_UNLIKELY(createNewListElementResult.isErr())) {
5050 NS_WARNING(
5051 nsPrintfCString(
5052 "HTMLEditor::InsertElementWithSplittingAncestorsWithTransaction("
5053 "%s) failed",
5054 nsAtomCString(containerName).get())
5055 .get());
5056 return createNewListElementResult.propagateErr();
5058 CreateElementResult unwrappedCreateNewListElementResult =
5059 createNewListElementResult.unwrap();
5060 MOZ_ASSERT(unwrappedCreateNewListElementResult.GetNewNode());
5061 pointToPutCaret = unwrappedCreateNewListElementResult.UnwrapCaretPoint();
5062 *aSubListElement = unwrappedCreateNewListElementResult.UnwrapNewNode();
5065 // Finally, we should move aContentMovingToSubList into aSubListElement.
5066 const RefPtr<Element> subListElement = *aSubListElement;
5067 Result<MoveNodeResult, nsresult> moveNodeResult =
5068 MoveNodeToEndWithTransaction(aContentMovingToSubList, *subListElement);
5069 if (MOZ_UNLIKELY(moveNodeResult.isErr())) {
5070 NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed");
5071 return moveNodeResult.propagateErr();
5073 MoveNodeResult unwrappedMoveNodeResult = moveNodeResult.unwrap();
5074 if (unwrappedMoveNodeResult.HasCaretPointSuggestion()) {
5075 pointToPutCaret = unwrappedMoveNodeResult.UnwrapCaretPoint();
5077 return pointToPutCaret;
5080 Result<EditActionResult, nsresult> HTMLEditor::HandleIndentAtSelection(
5081 const Element& aEditingHost) {
5082 MOZ_ASSERT(IsEditActionDataAvailable());
5083 MOZ_ASSERT(!IsSelectionRangeContainerNotContent());
5085 nsresult rv = EnsureNoPaddingBRElementForEmptyEditor();
5086 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
5087 return Err(NS_ERROR_EDITOR_DESTROYED);
5089 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
5090 "EditorBase::EnsureNoPaddingBRElementForEmptyEditor() "
5091 "failed, but ignored");
5093 if (NS_SUCCEEDED(rv) && SelectionRef().IsCollapsed()) {
5094 nsresult rv = EnsureCaretNotAfterInvisibleBRElement();
5095 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
5096 return Err(NS_ERROR_EDITOR_DESTROYED);
5098 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
5099 "HTMLEditor::EnsureCaretNotAfterInvisibleBRElement() "
5100 "failed, but ignored");
5101 if (NS_SUCCEEDED(rv)) {
5102 nsresult rv = PrepareInlineStylesForCaret();
5103 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
5104 return Err(NS_ERROR_EDITOR_DESTROYED);
5106 NS_WARNING_ASSERTION(
5107 NS_SUCCEEDED(rv),
5108 "HTMLEditor::PrepareInlineStylesForCaret() failed, but ignored");
5112 AutoRangeArray selectionRanges(SelectionRef());
5114 if (MOZ_UNLIKELY(!selectionRanges.IsInContent())) {
5115 NS_WARNING("Mutation event listener might have changed the selection");
5116 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
5119 if (IsCSSEnabled()) {
5120 nsresult rv = HandleCSSIndentAroundRanges(selectionRanges, aEditingHost);
5121 if (NS_FAILED(rv)) {
5122 NS_WARNING("HTMLEditor::HandleCSSIndentAroundRanges() failed");
5123 return Err(rv);
5125 } else {
5126 nsresult rv = HandleHTMLIndentAroundRanges(selectionRanges, aEditingHost);
5127 if (NS_FAILED(rv)) {
5128 NS_WARNING("HTMLEditor::HandleHTMLIndentAroundRanges() failed");
5129 return Err(rv);
5132 rv = selectionRanges.ApplyTo(SelectionRef());
5133 if (MOZ_UNLIKELY(Destroyed())) {
5134 NS_WARNING("AutoRangeArray::ApplyTo() caused destroying the editor");
5135 return Err(NS_ERROR_EDITOR_DESTROYED);
5137 if (NS_FAILED(rv)) {
5138 NS_WARNING("AutoRangeArray::ApplyTo() failed");
5139 return Err(rv);
5141 return EditActionResult::HandledResult();
5144 nsresult HTMLEditor::HandleCSSIndentAroundRanges(AutoRangeArray& aRanges,
5145 const Element& aEditingHost) {
5146 MOZ_ASSERT(IsEditActionDataAvailable());
5147 MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable());
5148 MOZ_ASSERT(!aRanges.Ranges().IsEmpty());
5149 MOZ_ASSERT(aRanges.IsInContent());
5151 if (aRanges.Ranges().IsEmpty()) {
5152 NS_WARNING("There is no selection range");
5153 return NS_ERROR_FAILURE;
5156 // XXX Why do we do this only when there is only one selection range?
5157 if (!aRanges.IsCollapsed() && aRanges.Ranges().Length() == 1u) {
5158 Result<EditorRawDOMRange, nsresult> extendedRange =
5159 GetRangeExtendedToHardLineEdgesForBlockEditAction(
5160 aRanges.FirstRangeRef(), aEditingHost);
5161 if (MOZ_UNLIKELY(extendedRange.isErr())) {
5162 NS_WARNING(
5163 "HTMLEditor::GetRangeExtendedToHardLineEdgesForBlockEditAction() "
5164 "failed");
5165 return extendedRange.unwrapErr();
5167 // Note that end point may be prior to start point. So, we
5168 // cannot use SetStartAndEnd() here.
5169 nsresult rv = aRanges.SetBaseAndExtent(extendedRange.inspect().StartRef(),
5170 extendedRange.inspect().EndRef());
5171 if (NS_FAILED(rv)) {
5172 NS_WARNING("AutoRangeArray::SetBaseAndExtent() failed");
5173 return rv;
5177 if (NS_WARN_IF(!aRanges.SaveAndTrackRanges(*this))) {
5178 return NS_ERROR_FAILURE;
5181 AutoTArray<OwningNonNull<nsIContent>, 64> arrayOfContents;
5183 // short circuit: detect case of collapsed selection inside an <li>.
5184 // just sublist that <li>. This prevents bug 97797.
5186 if (aRanges.IsCollapsed()) {
5187 const auto atCaret = aRanges.GetFirstRangeStartPoint<EditorRawDOMPoint>();
5188 if (NS_WARN_IF(!atCaret.IsSet())) {
5189 return NS_ERROR_FAILURE;
5191 MOZ_ASSERT(atCaret.IsInContentNode());
5192 Element* const editableBlockElement =
5193 HTMLEditUtils::GetInclusiveAncestorElement(
5194 *atCaret.ContainerAs<nsIContent>(),
5195 HTMLEditUtils::ClosestEditableBlockElement,
5196 BlockInlineCheck::UseHTMLDefaultStyle);
5197 if (editableBlockElement &&
5198 HTMLEditUtils::IsListItem(editableBlockElement)) {
5199 arrayOfContents.AppendElement(*editableBlockElement);
5203 EditorDOMPoint pointToPutCaret;
5204 if (arrayOfContents.IsEmpty()) {
5206 AutoRangeArray extendedRanges(aRanges);
5207 extendedRanges.ExtendRangesToWrapLines(
5208 EditSubAction::eIndent, BlockInlineCheck::UseHTMLDefaultStyle,
5209 aEditingHost);
5210 Result<EditorDOMPoint, nsresult> splitResult =
5211 extendedRanges
5212 .SplitTextAtEndBoundariesAndInlineAncestorsAtBothBoundaries(
5213 *this, BlockInlineCheck::UseHTMLDefaultStyle, aEditingHost);
5214 if (MOZ_UNLIKELY(splitResult.isErr())) {
5215 NS_WARNING(
5216 "AutoRangeArray::"
5217 "SplitTextAtEndBoundariesAndInlineAncestorsAtBothBoundaries() "
5218 "failed");
5219 return splitResult.unwrapErr();
5221 if (splitResult.inspect().IsSet()) {
5222 pointToPutCaret = splitResult.unwrap();
5224 nsresult rv = extendedRanges.CollectEditTargetNodes(
5225 *this, arrayOfContents, EditSubAction::eIndent,
5226 AutoRangeArray::CollectNonEditableNodes::Yes);
5227 if (NS_FAILED(rv)) {
5228 NS_WARNING(
5229 "AutoRangeArray::CollectEditTargetNodes(EditSubAction::eIndent, "
5230 "CollectNonEditableNodes::Yes) failed");
5231 return rv;
5234 Result<EditorDOMPoint, nsresult> splitAtBRElementsResult =
5235 MaybeSplitElementsAtEveryBRElement(arrayOfContents,
5236 EditSubAction::eIndent);
5237 if (MOZ_UNLIKELY(splitAtBRElementsResult.isErr())) {
5238 NS_WARNING(
5239 "HTMLEditor::MaybeSplitElementsAtEveryBRElement(EditSubAction::"
5240 "eIndent) failed");
5241 return splitAtBRElementsResult.inspectErr();
5243 if (splitAtBRElementsResult.inspect().IsSet()) {
5244 pointToPutCaret = splitAtBRElementsResult.unwrap();
5248 // If there is no visible and editable nodes in the edit targets, make an
5249 // empty block.
5250 // XXX Isn't this odd if there are only non-editable visible nodes?
5251 if (HTMLEditUtils::IsEmptyOneHardLine(
5252 arrayOfContents, BlockInlineCheck::UseHTMLDefaultStyle)) {
5253 const EditorDOMPoint pointToInsertDivElement =
5254 pointToPutCaret.IsSet()
5255 ? std::move(pointToPutCaret)
5256 : aRanges.GetFirstRangeStartPoint<EditorDOMPoint>();
5257 if (NS_WARN_IF(!pointToInsertDivElement.IsSet())) {
5258 return NS_ERROR_FAILURE;
5261 // make sure we can put a block here
5262 Result<CreateElementResult, nsresult> createNewDivElementResult =
5263 InsertElementWithSplittingAncestorsWithTransaction(
5264 *nsGkAtoms::div, pointToInsertDivElement,
5265 BRElementNextToSplitPoint::Keep, aEditingHost);
5266 if (MOZ_UNLIKELY(createNewDivElementResult.isErr())) {
5267 NS_WARNING(
5268 "HTMLEditor::InsertElementWithSplittingAncestorsWithTransaction("
5269 "nsGkAtoms::div) failed");
5270 return createNewDivElementResult.unwrapErr();
5272 CreateElementResult unwrappedCreateNewDivElementResult =
5273 createNewDivElementResult.unwrap();
5274 // We'll collapse ranges below, so we don't need to touch the ranges here.
5275 unwrappedCreateNewDivElementResult.IgnoreCaretPointSuggestion();
5276 const RefPtr<Element> newDivElement =
5277 unwrappedCreateNewDivElementResult.UnwrapNewNode();
5278 MOZ_ASSERT(newDivElement);
5279 const Result<EditorDOMPoint, nsresult> pointToPutCaretOrError =
5280 ChangeMarginStart(*newDivElement, ChangeMargin::Increase, aEditingHost);
5281 if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) {
5282 if (NS_WARN_IF(pointToPutCaretOrError.inspectErr() ==
5283 NS_ERROR_EDITOR_DESTROYED)) {
5284 return NS_ERROR_EDITOR_DESTROYED;
5286 NS_WARNING(
5287 "HTMLEditor::ChangeMarginStart(ChangeMargin::Increase) failed, but "
5288 "ignored");
5290 // delete anything that was in the list of nodes
5291 // XXX We don't need to remove the nodes from the array for performance.
5292 for (const OwningNonNull<nsIContent>& content : arrayOfContents) {
5293 // MOZ_KnownLive(content) due to bug 1622253
5294 nsresult rv = DeleteNodeWithTransaction(MOZ_KnownLive(content));
5295 if (NS_FAILED(rv)) {
5296 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
5297 return rv;
5300 aRanges.ClearSavedRanges();
5301 nsresult rv = aRanges.Collapse(EditorDOMPoint(newDivElement, 0u));
5302 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "AutoRangeArray::Collapse() failed");
5303 return rv;
5306 RefPtr<Element> latestNewBlockElement;
5307 auto RestoreSavedRangesAndCollapseInLatestBlockElementIfOutside =
5308 [&]() -> nsresult {
5309 MOZ_ASSERT(aRanges.HasSavedRanges());
5310 aRanges.RestoreFromSavedRanges();
5312 if (!latestNewBlockElement || !aRanges.IsCollapsed() ||
5313 aRanges.Ranges().IsEmpty()) {
5314 return NS_OK;
5317 const auto firstRangeStartRawPoint =
5318 aRanges.GetFirstRangeStartPoint<EditorRawDOMPoint>();
5319 if (MOZ_UNLIKELY(!firstRangeStartRawPoint.IsSet())) {
5320 return NS_OK;
5322 Result<EditorRawDOMPoint, nsresult> pointInNewBlockElementOrError =
5323 HTMLEditUtils::ComputePointToPutCaretInElementIfOutside<
5324 EditorRawDOMPoint>(*latestNewBlockElement, firstRangeStartRawPoint);
5325 if (MOZ_UNLIKELY(pointInNewBlockElementOrError.isErr())) {
5326 NS_WARNING(
5327 "HTMLEditUtils::ComputePointToPutCaretInElementIfOutside() failed, "
5328 "but ignored");
5329 return NS_OK;
5331 if (!pointInNewBlockElementOrError.inspect().IsSet()) {
5332 return NS_OK;
5334 return aRanges.Collapse(pointInNewBlockElementOrError.unwrap());
5337 // Ok, now go through all the nodes and put them into sub-list element
5338 // elements and new <div> elements which have start margin.
5339 RefPtr<Element> subListElement, divElement;
5340 for (OwningNonNull<nsIContent>& content : arrayOfContents) {
5341 // Here's where we actually figure out what to do.
5342 EditorDOMPoint atContent(content);
5343 if (NS_WARN_IF(!atContent.IsSet())) {
5344 continue;
5347 // Ignore all non-editable nodes. Leave them be.
5348 // XXX We ignore non-editable nodes here, but not so in the above block.
5349 if (!EditorUtils::IsEditableContent(content, EditorType::HTML)) {
5350 continue;
5353 if (HTMLEditUtils::IsAnyListElement(atContent.GetContainer())) {
5354 const RefPtr<Element> oldSubListElement = subListElement;
5355 // MOZ_KnownLive because 'arrayOfContents' is guaranteed to
5356 // keep it alive.
5357 Result<EditorDOMPoint, nsresult> pointToPutCaretOrError =
5358 IndentListChildWithTransaction(&subListElement, atContent,
5359 MOZ_KnownLive(content), aEditingHost);
5360 if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) {
5361 NS_WARNING("HTMLEditor::IndentListChildWithTransaction() failed");
5362 return pointToPutCaretOrError.unwrapErr();
5364 if (subListElement != oldSubListElement) {
5365 // New list element is created, so we should put caret into the new list
5366 // element.
5367 latestNewBlockElement = subListElement;
5369 if (pointToPutCaretOrError.inspect().IsSet()) {
5370 pointToPutCaret = pointToPutCaretOrError.unwrap();
5372 continue;
5375 // Not a list item.
5377 if (HTMLEditUtils::IsBlockElement(content,
5378 BlockInlineCheck::UseHTMLDefaultStyle)) {
5379 Result<EditorDOMPoint, nsresult> pointToPutCaretOrError =
5380 ChangeMarginStart(MOZ_KnownLive(*content->AsElement()),
5381 ChangeMargin::Increase, aEditingHost);
5382 if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) {
5383 if (MOZ_UNLIKELY(pointToPutCaretOrError.inspectErr() ==
5384 NS_ERROR_EDITOR_DESTROYED)) {
5385 NS_WARNING(
5386 "HTMLEditor::ChangeMarginStart(ChangeMargin::Increase) failed");
5387 return NS_ERROR_EDITOR_DESTROYED;
5389 NS_WARNING(
5390 "HTMLEditor::ChangeMarginStart(ChangeMargin::Increase) failed, but "
5391 "ignored");
5392 } else if (pointToPutCaretOrError.inspect().IsSet()) {
5393 pointToPutCaret = pointToPutCaretOrError.unwrap();
5395 divElement = nullptr;
5396 continue;
5399 if (!divElement) {
5400 // First, check that our element can contain a div.
5401 if (!HTMLEditUtils::CanNodeContain(*atContent.GetContainer(),
5402 *nsGkAtoms::div)) {
5403 // XXX This is odd, why do we stop indenting remaining content nodes?
5404 // Perhaps, `continue` is better.
5405 nsresult rv =
5406 RestoreSavedRangesAndCollapseInLatestBlockElementIfOutside();
5407 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
5408 "RestoreSavedRangesAndCollapseInLatestBlockElement"
5409 "IfOutside() failed");
5410 return rv;
5413 Result<CreateElementResult, nsresult> createNewDivElementResult =
5414 InsertElementWithSplittingAncestorsWithTransaction(
5415 *nsGkAtoms::div, atContent, BRElementNextToSplitPoint::Keep,
5416 aEditingHost);
5417 if (MOZ_UNLIKELY(createNewDivElementResult.isErr())) {
5418 NS_WARNING(
5419 "HTMLEditor::InsertElementWithSplittingAncestorsWithTransaction("
5420 "nsGkAtoms::div) failed");
5421 return createNewDivElementResult.unwrapErr();
5423 CreateElementResult unwrappedCreateNewDivElementResult =
5424 createNewDivElementResult.unwrap();
5425 pointToPutCaret = unwrappedCreateNewDivElementResult.UnwrapCaretPoint();
5427 MOZ_ASSERT(unwrappedCreateNewDivElementResult.GetNewNode());
5428 divElement = unwrappedCreateNewDivElementResult.UnwrapNewNode();
5429 Result<EditorDOMPoint, nsresult> pointToPutCaretOrError =
5430 ChangeMarginStart(*divElement, ChangeMargin::Increase, aEditingHost);
5431 if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) {
5432 if (MOZ_UNLIKELY(pointToPutCaretOrError.inspectErr() ==
5433 NS_ERROR_EDITOR_DESTROYED)) {
5434 NS_WARNING(
5435 "HTMLEditor::ChangeMarginStart(ChangeMargin::Increase) failed");
5436 return NS_ERROR_EDITOR_DESTROYED;
5438 NS_WARNING(
5439 "HTMLEditor::ChangeMarginStart(ChangeMargin::Increase) failed, but "
5440 "ignored");
5441 } else if (AllowsTransactionsToChangeSelection() &&
5442 pointToPutCaretOrError.inspect().IsSet()) {
5443 pointToPutCaret = pointToPutCaretOrError.unwrap();
5446 latestNewBlockElement = divElement;
5449 // Move the content into the <div> which has start margin.
5450 // MOZ_KnownLive because 'arrayOfContents' is guaranteed to
5451 // keep it alive.
5452 Result<MoveNodeResult, nsresult> moveNodeResult =
5453 MoveNodeToEndWithTransaction(MOZ_KnownLive(content), *divElement);
5454 if (MOZ_UNLIKELY(moveNodeResult.isErr())) {
5455 NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed");
5456 return moveNodeResult.unwrapErr();
5458 MoveNodeResult unwrappedMoveNodeResult = moveNodeResult.unwrap();
5459 if (unwrappedMoveNodeResult.HasCaretPointSuggestion()) {
5460 pointToPutCaret = unwrappedMoveNodeResult.UnwrapCaretPoint();
5464 nsresult rv = RestoreSavedRangesAndCollapseInLatestBlockElementIfOutside();
5465 NS_WARNING_ASSERTION(
5466 NS_SUCCEEDED(rv),
5467 "RestoreSavedRangesAndCollapseInLatestBlockElementIfOutside() failed");
5468 return rv;
5471 nsresult HTMLEditor::HandleHTMLIndentAroundRanges(AutoRangeArray& aRanges,
5472 const Element& aEditingHost) {
5473 MOZ_ASSERT(IsEditActionDataAvailable());
5474 MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable());
5475 MOZ_ASSERT(!aRanges.Ranges().IsEmpty());
5476 MOZ_ASSERT(aRanges.IsInContent());
5478 // XXX Why do we do this only when there is only one range?
5479 if (!aRanges.IsCollapsed() && aRanges.Ranges().Length() == 1u) {
5480 Result<EditorRawDOMRange, nsresult> extendedRange =
5481 GetRangeExtendedToHardLineEdgesForBlockEditAction(
5482 aRanges.FirstRangeRef(), aEditingHost);
5483 if (MOZ_UNLIKELY(extendedRange.isErr())) {
5484 NS_WARNING(
5485 "HTMLEditor::GetRangeExtendedToHardLineEdgesForBlockEditAction() "
5486 "failed");
5487 return extendedRange.unwrapErr();
5489 // Note that end point may be prior to start point. So, we cannot use
5490 // SetStartAndEnd() here.
5491 nsresult rv = aRanges.SetBaseAndExtent(extendedRange.inspect().StartRef(),
5492 extendedRange.inspect().EndRef());
5493 if (NS_FAILED(rv)) {
5494 NS_WARNING("AutoRangeArray::SetBaseAndExtent() failed");
5495 return rv;
5499 if (NS_WARN_IF(!aRanges.SaveAndTrackRanges(*this))) {
5500 return NS_ERROR_FAILURE;
5503 EditorDOMPoint pointToPutCaret;
5505 // convert the selection ranges into "promoted" selection ranges:
5506 // this basically just expands the range to include the immediate
5507 // block parent, and then further expands to include any ancestors
5508 // whose children are all in the range
5510 // use these ranges to construct a list of nodes to act on.
5511 AutoTArray<OwningNonNull<nsIContent>, 64> arrayOfContents;
5513 AutoRangeArray extendedRanges(aRanges);
5514 extendedRanges.ExtendRangesToWrapLines(
5515 EditSubAction::eIndent, BlockInlineCheck::UseHTMLDefaultStyle,
5516 aEditingHost);
5517 Result<EditorDOMPoint, nsresult> splitResult =
5518 extendedRanges
5519 .SplitTextAtEndBoundariesAndInlineAncestorsAtBothBoundaries(
5520 *this, BlockInlineCheck::UseHTMLDefaultStyle, aEditingHost);
5521 if (MOZ_UNLIKELY(splitResult.isErr())) {
5522 NS_WARNING(
5523 "AutoRangeArray::"
5524 "SplitTextAtEndBoundariesAndInlineAncestorsAtBothBoundaries() "
5525 "failed");
5526 return splitResult.unwrapErr();
5528 if (splitResult.inspect().IsSet()) {
5529 pointToPutCaret = splitResult.unwrap();
5531 nsresult rv = extendedRanges.CollectEditTargetNodes(
5532 *this, arrayOfContents, EditSubAction::eIndent,
5533 AutoRangeArray::CollectNonEditableNodes::Yes);
5534 if (NS_FAILED(rv)) {
5535 NS_WARNING(
5536 "AutoRangeArray::CollectEditTargetNodes(EditSubAction::eIndent, "
5537 "CollectNonEditableNodes::Yes) failed");
5538 return rv;
5542 // FIXME: Split ancestors when we consider to indent the range.
5543 Result<EditorDOMPoint, nsresult> splitAtBRElementsResult =
5544 MaybeSplitElementsAtEveryBRElement(arrayOfContents,
5545 EditSubAction::eIndent);
5546 if (MOZ_UNLIKELY(splitAtBRElementsResult.isErr())) {
5547 NS_WARNING(
5548 "HTMLEditor::MaybeSplitElementsAtEveryBRElement(EditSubAction::eIndent)"
5549 " failed");
5550 return splitAtBRElementsResult.inspectErr();
5552 if (splitAtBRElementsResult.inspect().IsSet()) {
5553 pointToPutCaret = splitAtBRElementsResult.unwrap();
5556 // If there is no visible and editable nodes in the edit targets, make an
5557 // empty block.
5558 // XXX Isn't this odd if there are only non-editable visible nodes?
5559 if (HTMLEditUtils::IsEmptyOneHardLine(
5560 arrayOfContents, BlockInlineCheck::UseHTMLDefaultStyle)) {
5561 const EditorDOMPoint pointToInsertBlockquoteElement =
5562 pointToPutCaret.IsSet()
5563 ? std::move(pointToPutCaret)
5564 : EditorBase::GetFirstSelectionStartPoint<EditorDOMPoint>();
5565 if (NS_WARN_IF(!pointToInsertBlockquoteElement.IsSet())) {
5566 return NS_ERROR_FAILURE;
5569 // If there is no element which can have <blockquote>, abort.
5570 if (NS_WARN_IF(!HTMLEditUtils::GetInsertionPointInInclusiveAncestor(
5571 *nsGkAtoms::blockquote, pointToInsertBlockquoteElement,
5572 &aEditingHost)
5573 .IsSet())) {
5574 return NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE;
5577 // Make sure we can put a block here.
5578 // XXX Unfortunately, this calls
5579 // MaybeSplitAncestorsForInsertWithTransaction() then,
5580 // HTMLEditUtils::GetInsertionPointInInclusiveAncestor() is called again.
5581 Result<CreateElementResult, nsresult> createNewBlockquoteElementResult =
5582 InsertElementWithSplittingAncestorsWithTransaction(
5583 *nsGkAtoms::blockquote, pointToInsertBlockquoteElement,
5584 BRElementNextToSplitPoint::Keep, aEditingHost);
5585 if (MOZ_UNLIKELY(createNewBlockquoteElementResult.isErr())) {
5586 NS_WARNING(
5587 "HTMLEditor::InsertElementWithSplittingAncestorsWithTransaction("
5588 "nsGkAtoms::blockquote) failed");
5589 return createNewBlockquoteElementResult.unwrapErr();
5591 CreateElementResult unwrappedCreateNewBlockquoteElementResult =
5592 createNewBlockquoteElementResult.unwrap();
5593 unwrappedCreateNewBlockquoteElementResult.IgnoreCaretPointSuggestion();
5594 RefPtr<Element> newBlockquoteElement =
5595 unwrappedCreateNewBlockquoteElementResult.UnwrapNewNode();
5596 MOZ_ASSERT(newBlockquoteElement);
5597 // delete anything that was in the list of nodes
5598 // XXX We don't need to remove the nodes from the array for performance.
5599 for (const OwningNonNull<nsIContent>& content : arrayOfContents) {
5600 // MOZ_KnownLive because 'arrayOfContents' is guaranteed to
5601 // keep it alive.
5602 nsresult rv = DeleteNodeWithTransaction(MOZ_KnownLive(*content));
5603 if (NS_FAILED(rv)) {
5604 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
5605 return rv;
5608 aRanges.ClearSavedRanges();
5609 nsresult rv = aRanges.Collapse(EditorRawDOMPoint(newBlockquoteElement, 0u));
5610 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
5611 "EditorBase::CollapseSelectionToStartOf() failed");
5612 return rv;
5615 RefPtr<Element> latestNewBlockElement;
5616 auto RestoreSavedRangesAndCollapseInLatestBlockElementIfOutside =
5617 [&]() -> nsresult {
5618 MOZ_ASSERT(aRanges.HasSavedRanges());
5619 aRanges.RestoreFromSavedRanges();
5621 if (!latestNewBlockElement || !aRanges.IsCollapsed() ||
5622 aRanges.Ranges().IsEmpty()) {
5623 return NS_OK;
5626 const auto firstRangeStartRawPoint =
5627 aRanges.GetFirstRangeStartPoint<EditorRawDOMPoint>();
5628 if (MOZ_UNLIKELY(!firstRangeStartRawPoint.IsSet())) {
5629 return NS_OK;
5631 Result<EditorRawDOMPoint, nsresult> pointInNewBlockElementOrError =
5632 HTMLEditUtils::ComputePointToPutCaretInElementIfOutside<
5633 EditorRawDOMPoint>(*latestNewBlockElement, firstRangeStartRawPoint);
5634 if (MOZ_UNLIKELY(pointInNewBlockElementOrError.isErr())) {
5635 NS_WARNING(
5636 "HTMLEditUtils::ComputePointToPutCaretInElementIfOutside() failed, "
5637 "but ignored");
5638 return NS_OK;
5640 if (!pointInNewBlockElementOrError.inspect().IsSet()) {
5641 return NS_OK;
5643 return aRanges.Collapse(pointInNewBlockElementOrError.unwrap());
5646 // Ok, now go through all the nodes and put them in a blockquote,
5647 // or whatever is appropriate. Wohoo!
5648 RefPtr<Element> subListElement, blockquoteElement, indentedListItemElement;
5649 for (OwningNonNull<nsIContent>& content : arrayOfContents) {
5650 // Here's where we actually figure out what to do.
5651 EditorDOMPoint atContent(content);
5652 if (NS_WARN_IF(!atContent.IsSet())) {
5653 continue;
5656 // Ignore all non-editable nodes. Leave them be.
5657 // XXX We ignore non-editable nodes here, but not so in the above block.
5658 if (!EditorUtils::IsEditableContent(content, EditorType::HTML) ||
5659 !HTMLEditUtils::IsRemovableNode(content)) {
5660 continue;
5663 // If the content has been moved to different place, ignore it.
5664 if (MOZ_UNLIKELY(!content->IsInclusiveDescendantOf(&aEditingHost))) {
5665 continue;
5668 if (HTMLEditUtils::IsAnyListElement(atContent.GetContainer())) {
5669 const RefPtr<Element> oldSubListElement = subListElement;
5670 // MOZ_KnownLive because 'arrayOfContents' is guaranteed to
5671 // keep it alive.
5672 Result<EditorDOMPoint, nsresult> pointToPutCaretOrError =
5673 IndentListChildWithTransaction(&subListElement, atContent,
5674 MOZ_KnownLive(content), aEditingHost);
5675 if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) {
5676 NS_WARNING("HTMLEditor::IndentListChildWithTransaction() failed");
5677 return pointToPutCaretOrError.unwrapErr();
5679 if (oldSubListElement != subListElement) {
5680 // New list element is created, so we should put caret into the new list
5681 // element.
5682 latestNewBlockElement = subListElement;
5684 if (pointToPutCaretOrError.inspect().IsSet()) {
5685 pointToPutCaret = pointToPutCaretOrError.unwrap();
5687 blockquoteElement = nullptr;
5688 continue;
5691 // Not a list item, use blockquote?
5693 // if we are inside a list item, we don't want to blockquote, we want
5694 // to sublist the list item. We may have several nodes listed in the
5695 // array of nodes to act on, that are in the same list item. Since
5696 // we only want to indent that li once, we must keep track of the most
5697 // recent indented list item, and not indent it if we find another node
5698 // to act on that is still inside the same li.
5699 if (RefPtr<Element> listItem =
5700 HTMLEditUtils::GetClosestAncestorListItemElement(content,
5701 &aEditingHost)) {
5702 if (indentedListItemElement == listItem) {
5703 // already indented this list item
5704 continue;
5706 // check to see if subListElement is still appropriate. Which it is if
5707 // content is still right after it in the same list.
5708 nsIContent* previousEditableSibling =
5709 subListElement
5710 ? HTMLEditUtils::GetPreviousSibling(
5711 *listItem, {WalkTreeOption::IgnoreNonEditableNode})
5712 : nullptr;
5713 if (!subListElement || (previousEditableSibling &&
5714 previousEditableSibling != subListElement)) {
5715 EditorDOMPoint atListItem(listItem);
5716 if (NS_WARN_IF(!listItem)) {
5717 return NS_ERROR_FAILURE;
5719 nsAtom* containerName =
5720 atListItem.GetContainer()->NodeInfo()->NameAtom();
5721 // Create a new nested list of correct type.
5722 Result<CreateElementResult, nsresult> createNewListElementResult =
5723 InsertElementWithSplittingAncestorsWithTransaction(
5724 MOZ_KnownLive(*containerName), atListItem,
5725 BRElementNextToSplitPoint::Keep, aEditingHost);
5726 if (MOZ_UNLIKELY(createNewListElementResult.isErr())) {
5727 NS_WARNING(nsPrintfCString("HTMLEditor::"
5728 "InsertElementWithSplittingAncestorsWithTr"
5729 "ansaction(%s) failed",
5730 nsAtomCString(containerName).get())
5731 .get());
5732 return createNewListElementResult.unwrapErr();
5734 CreateElementResult unwrappedCreateNewListElementResult =
5735 createNewListElementResult.unwrap();
5736 if (unwrappedCreateNewListElementResult.HasCaretPointSuggestion()) {
5737 pointToPutCaret =
5738 unwrappedCreateNewListElementResult.UnwrapCaretPoint();
5740 MOZ_ASSERT(unwrappedCreateNewListElementResult.GetNewNode());
5741 subListElement = unwrappedCreateNewListElementResult.UnwrapNewNode();
5744 Result<MoveNodeResult, nsresult> moveListItemElementResult =
5745 MoveNodeToEndWithTransaction(*listItem, *subListElement);
5746 if (MOZ_UNLIKELY(moveListItemElementResult.isErr())) {
5747 NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed");
5748 return moveListItemElementResult.unwrapErr();
5750 MoveNodeResult unwrappedMoveListItemElementResult =
5751 moveListItemElementResult.unwrap();
5752 if (unwrappedMoveListItemElementResult.HasCaretPointSuggestion()) {
5753 pointToPutCaret = unwrappedMoveListItemElementResult.UnwrapCaretPoint();
5756 // Remember the list item element which we indented now for ignoring its
5757 // children to avoid using <blockquote> in it.
5758 indentedListItemElement = std::move(listItem);
5760 continue;
5763 // need to make a blockquote to put things in if we haven't already,
5764 // or if this node doesn't go in blockquote we used earlier.
5765 // One reason it might not go in prio blockquote is if we are now
5766 // in a different table cell.
5767 if (blockquoteElement &&
5768 HTMLEditUtils::GetInclusiveAncestorAnyTableElement(
5769 *blockquoteElement) !=
5770 HTMLEditUtils::GetInclusiveAncestorAnyTableElement(content)) {
5771 blockquoteElement = nullptr;
5774 if (!blockquoteElement) {
5775 // First, check that our element can contain a blockquote.
5776 if (!HTMLEditUtils::CanNodeContain(*atContent.GetContainer(),
5777 *nsGkAtoms::blockquote)) {
5778 // XXX This is odd, why do we stop indenting remaining content nodes?
5779 // Perhaps, `continue` is better.
5780 nsresult rv =
5781 RestoreSavedRangesAndCollapseInLatestBlockElementIfOutside();
5782 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
5783 "RestoreSavedRangesAndCollapseInLatestBlockElement"
5784 "IfOutside() failed");
5785 return rv;
5788 Result<CreateElementResult, nsresult> createNewBlockquoteElementResult =
5789 InsertElementWithSplittingAncestorsWithTransaction(
5790 *nsGkAtoms::blockquote, atContent,
5791 BRElementNextToSplitPoint::Keep, aEditingHost);
5792 if (MOZ_UNLIKELY(createNewBlockquoteElementResult.isErr())) {
5793 NS_WARNING(
5794 "HTMLEditor::InsertElementWithSplittingAncestorsWithTransaction("
5795 "nsGkAtoms::blockquote) failed");
5796 return createNewBlockquoteElementResult.unwrapErr();
5798 CreateElementResult unwrappedCreateNewBlockquoteElementResult =
5799 createNewBlockquoteElementResult.unwrap();
5800 if (unwrappedCreateNewBlockquoteElementResult.HasCaretPointSuggestion()) {
5801 pointToPutCaret =
5802 unwrappedCreateNewBlockquoteElementResult.UnwrapCaretPoint();
5805 MOZ_ASSERT(unwrappedCreateNewBlockquoteElementResult.GetNewNode());
5806 blockquoteElement =
5807 unwrappedCreateNewBlockquoteElementResult.UnwrapNewNode();
5808 latestNewBlockElement = blockquoteElement;
5811 // tuck the node into the end of the active blockquote
5812 // MOZ_KnownLive because 'arrayOfContents' is guaranteed to
5813 // keep it alive.
5814 Result<MoveNodeResult, nsresult> moveNodeResult =
5815 MoveNodeToEndWithTransaction(MOZ_KnownLive(content),
5816 *blockquoteElement);
5817 if (MOZ_UNLIKELY(moveNodeResult.isErr())) {
5818 NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed");
5819 return moveNodeResult.unwrapErr();
5821 MoveNodeResult unwrappedMoveNodeResult = moveNodeResult.unwrap();
5822 if (unwrappedMoveNodeResult.HasCaretPointSuggestion()) {
5823 pointToPutCaret = unwrappedMoveNodeResult.UnwrapCaretPoint();
5825 subListElement = nullptr;
5828 nsresult rv = RestoreSavedRangesAndCollapseInLatestBlockElementIfOutside();
5829 NS_WARNING_ASSERTION(
5830 NS_SUCCEEDED(rv),
5831 "RestoreSavedRangesAndCollapseInLatestBlockElementIfOutside() failed");
5832 return rv;
5835 Result<EditActionResult, nsresult> HTMLEditor::OutdentAsSubAction(
5836 const Element& aEditingHost) {
5837 MOZ_ASSERT(IsEditActionDataAvailable());
5839 AutoPlaceholderBatch treatAsOneTransaction(
5840 *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
5841 IgnoredErrorResult ignoredError;
5842 AutoEditSubActionNotifier startToHandleEditSubAction(
5843 *this, EditSubAction::eOutdent, nsIEditor::eNext, ignoredError);
5844 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
5845 return Err(ignoredError.StealNSResult());
5847 NS_WARNING_ASSERTION(
5848 !ignoredError.Failed(),
5849 "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
5852 Result<EditActionResult, nsresult> result = CanHandleHTMLEditSubAction();
5853 if (MOZ_UNLIKELY(result.isErr())) {
5854 NS_WARNING("HTMLEditor::CanHandleHTMLEditSubAction() failed");
5855 return result;
5857 if (result.inspect().Canceled()) {
5858 return result;
5862 if (MOZ_UNLIKELY(IsSelectionRangeContainerNotContent())) {
5863 NS_WARNING("Some selection containers are not content node, but ignored");
5864 return EditActionResult::IgnoredResult();
5867 Result<EditActionResult, nsresult> result =
5868 HandleOutdentAtSelection(aEditingHost);
5869 if (MOZ_UNLIKELY(result.isErr())) {
5870 NS_WARNING("HTMLEditor::HandleOutdentAtSelection() failed");
5871 return result;
5873 if (result.inspect().Canceled()) {
5874 return result;
5877 if (MOZ_UNLIKELY(IsSelectionRangeContainerNotContent())) {
5878 NS_WARNING("Mutation event listener might have changed the selection");
5879 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
5882 nsresult rv = MaybeInsertPaddingBRElementForEmptyLastLineAtSelection();
5883 if (NS_FAILED(rv)) {
5884 NS_WARNING(
5885 "HTMLEditor::MaybeInsertPaddingBRElementForEmptyLastLineAtSelection() "
5886 "failed");
5887 return Err(rv);
5889 return result;
5892 Result<EditActionResult, nsresult> HTMLEditor::HandleOutdentAtSelection(
5893 const Element& aEditingHost) {
5894 MOZ_ASSERT(IsEditActionDataAvailable());
5895 MOZ_ASSERT(!IsSelectionRangeContainerNotContent());
5897 // XXX Why do we do this only when there is only one selection range?
5898 if (!SelectionRef().IsCollapsed() && SelectionRef().RangeCount() == 1u) {
5899 Result<EditorRawDOMRange, nsresult> extendedRange =
5900 GetRangeExtendedToHardLineEdgesForBlockEditAction(
5901 SelectionRef().GetRangeAt(0u), aEditingHost);
5902 if (MOZ_UNLIKELY(extendedRange.isErr())) {
5903 NS_WARNING(
5904 "HTMLEditor::GetRangeExtendedToHardLineEdgesForBlockEditAction() "
5905 "failed");
5906 return extendedRange.propagateErr();
5908 // Note that end point may be prior to start point. So, we
5909 // cannot use Selection::SetStartAndEndInLimit() here.
5910 IgnoredErrorResult error;
5911 SelectionRef().SetBaseAndExtentInLimiter(
5912 extendedRange.inspect().StartRef().ToRawRangeBoundary(),
5913 extendedRange.inspect().EndRef().ToRawRangeBoundary(), error);
5914 if (NS_WARN_IF(Destroyed())) {
5915 return Err(NS_ERROR_EDITOR_DESTROYED);
5917 if (MOZ_UNLIKELY(error.Failed())) {
5918 NS_WARNING("Selection::SetBaseAndExtentInLimiter() failed");
5919 return Err(error.StealNSResult());
5923 // HandleOutdentAtSelectionInternal() creates AutoSelectionRestorer.
5924 // Therefore, even if it returns NS_OK, the editor might have been destroyed
5925 // at restoring Selection.
5926 Result<SplitRangeOffFromNodeResult, nsresult> outdentResult =
5927 HandleOutdentAtSelectionInternal(aEditingHost);
5928 MOZ_ASSERT_IF(outdentResult.isOk(),
5929 !outdentResult.inspect().HasCaretPointSuggestion());
5930 if (NS_WARN_IF(Destroyed())) {
5931 return Err(NS_ERROR_EDITOR_DESTROYED);
5933 if (MOZ_UNLIKELY(outdentResult.isErr())) {
5934 NS_WARNING("HTMLEditor::HandleOutdentAtSelectionInternal() failed");
5935 return outdentResult.propagateErr();
5937 SplitRangeOffFromNodeResult unwrappedOutdentResult = outdentResult.unwrap();
5939 // Make sure selection didn't stick to last piece of content in old bq (only
5940 // a problem for collapsed selections)
5941 if (!unwrappedOutdentResult.GetLeftContent() &&
5942 !unwrappedOutdentResult.GetRightContent()) {
5943 return EditActionResult::HandledResult();
5946 if (!SelectionRef().IsCollapsed()) {
5947 return EditActionResult::HandledResult();
5950 // Push selection past end of left element of last split indented element.
5951 if (unwrappedOutdentResult.GetLeftContent()) {
5952 const nsRange* firstRange = SelectionRef().GetRangeAt(0);
5953 if (NS_WARN_IF(!firstRange)) {
5954 return EditActionResult::HandledResult();
5956 const RangeBoundary& atStartOfSelection = firstRange->StartRef();
5957 if (NS_WARN_IF(!atStartOfSelection.IsSet())) {
5958 return Err(NS_ERROR_FAILURE);
5960 if (atStartOfSelection.Container() ==
5961 unwrappedOutdentResult.GetLeftContent() ||
5962 EditorUtils::IsDescendantOf(*atStartOfSelection.Container(),
5963 *unwrappedOutdentResult.GetLeftContent())) {
5964 // Selection is inside the left node - push it past it.
5965 EditorRawDOMPoint afterRememberedLeftBQ(
5966 EditorRawDOMPoint::After(*unwrappedOutdentResult.GetLeftContent()));
5967 NS_WARNING_ASSERTION(
5968 afterRememberedLeftBQ.IsSet(),
5969 "Failed to set after remembered left blockquote element");
5970 nsresult rv = CollapseSelectionTo(afterRememberedLeftBQ);
5971 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
5972 return Err(NS_ERROR_EDITOR_DESTROYED);
5974 NS_WARNING_ASSERTION(
5975 NS_SUCCEEDED(rv),
5976 "EditorBase::CollapseSelectionTo() failed, but ignored");
5979 // And pull selection before beginning of right element of last split
5980 // indented element.
5981 if (unwrappedOutdentResult.GetRightContent()) {
5982 const nsRange* firstRange = SelectionRef().GetRangeAt(0);
5983 if (NS_WARN_IF(!firstRange)) {
5984 return EditActionResult::HandledResult();
5986 const RangeBoundary& atStartOfSelection = firstRange->StartRef();
5987 if (NS_WARN_IF(!atStartOfSelection.IsSet())) {
5988 return Err(NS_ERROR_FAILURE);
5990 if (atStartOfSelection.Container() ==
5991 unwrappedOutdentResult.GetRightContent() ||
5992 EditorUtils::IsDescendantOf(
5993 *atStartOfSelection.Container(),
5994 *unwrappedOutdentResult.GetRightContent())) {
5995 // Selection is inside the right element - push it before it.
5996 EditorRawDOMPoint atRememberedRightBQ(
5997 unwrappedOutdentResult.GetRightContent());
5998 nsresult rv = CollapseSelectionTo(atRememberedRightBQ);
5999 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
6000 return Err(NS_ERROR_EDITOR_DESTROYED);
6002 NS_WARNING_ASSERTION(
6003 NS_SUCCEEDED(rv),
6004 "EditorBase::CollapseSelectionTo() failed, but ignored");
6007 return EditActionResult::HandledResult();
6010 Result<SplitRangeOffFromNodeResult, nsresult>
6011 HTMLEditor::HandleOutdentAtSelectionInternal(const Element& aEditingHost) {
6012 MOZ_ASSERT(IsEditActionDataAvailable());
6014 AutoSelectionRestorer restoreSelectionLater(*this);
6016 bool useCSS = IsCSSEnabled();
6018 // Convert the selection ranges into "promoted" selection ranges: this
6019 // basically just expands the range to include the immediate block parent,
6020 // and then further expands to include any ancestors whose children are all
6021 // in the range
6022 AutoTArray<OwningNonNull<nsIContent>, 64> arrayOfContents;
6024 AutoRangeArray extendedSelectionRanges(SelectionRef());
6025 extendedSelectionRanges.ExtendRangesToWrapLines(
6026 EditSubAction::eOutdent, BlockInlineCheck::UseHTMLDefaultStyle,
6027 aEditingHost);
6028 nsresult rv = extendedSelectionRanges.CollectEditTargetNodes(
6029 *this, arrayOfContents, EditSubAction::eOutdent,
6030 AutoRangeArray::CollectNonEditableNodes::Yes);
6031 if (NS_FAILED(rv)) {
6032 NS_WARNING(
6033 "AutoRangeArray::CollectEditTargetNodes(EditSubAction::eOutdent, "
6034 "CollectNonEditableNodes::Yes) failed");
6035 return Err(rv);
6037 Result<EditorDOMPoint, nsresult> splitAtBRElementsResult =
6038 MaybeSplitElementsAtEveryBRElement(arrayOfContents,
6039 EditSubAction::eOutdent);
6040 if (MOZ_UNLIKELY(splitAtBRElementsResult.isErr())) {
6041 NS_WARNING(
6042 "HTMLEditor::MaybeSplitElementsAtEveryBRElement(EditSubAction::"
6043 "eOutdent) failed");
6044 return splitAtBRElementsResult.propagateErr();
6046 if (AllowsTransactionsToChangeSelection() &&
6047 splitAtBRElementsResult.inspect().IsSet()) {
6048 nsresult rv = CollapseSelectionTo(splitAtBRElementsResult.inspect());
6049 if (NS_FAILED(rv)) {
6050 NS_WARNING("EditorBase::CollapseSelectionTo() failed");
6051 return Err(rv);
6056 nsCOMPtr<nsIContent> leftContentOfLastOutdented;
6057 nsCOMPtr<nsIContent> middleContentOfLastOutdented;
6058 nsCOMPtr<nsIContent> rightContentOfLastOutdented;
6059 RefPtr<Element> indentedParentElement;
6060 nsCOMPtr<nsIContent> firstContentToBeOutdented, lastContentToBeOutdented;
6061 BlockIndentedWith indentedParentIndentedWith = BlockIndentedWith::HTML;
6062 for (OwningNonNull<nsIContent>& content : arrayOfContents) {
6063 // Here's where we actually figure out what to do
6064 EditorDOMPoint atContent(content);
6065 if (!atContent.IsSet()) {
6066 continue;
6069 // If it's a `<blockquote>`, remove it to outdent its children.
6070 if (content->IsHTMLElement(nsGkAtoms::blockquote)) {
6071 // If we've already found an ancestor block element indented, we need to
6072 // split it and remove the block element first.
6073 if (indentedParentElement) {
6074 NS_WARNING_ASSERTION(indentedParentElement == content,
6075 "Indented parent element is not the <blockquote>");
6076 Result<SplitRangeOffFromNodeResult, nsresult> outdentResult =
6077 OutdentPartOfBlock(*indentedParentElement,
6078 *firstContentToBeOutdented,
6079 *lastContentToBeOutdented,
6080 indentedParentIndentedWith, aEditingHost);
6081 if (MOZ_UNLIKELY(outdentResult.isErr())) {
6082 NS_WARNING("HTMLEditor::OutdentPartOfBlock() failed");
6083 return outdentResult;
6085 SplitRangeOffFromNodeResult unwrappedOutdentResult =
6086 outdentResult.unwrap();
6087 unwrappedOutdentResult.IgnoreCaretPointSuggestion();
6088 leftContentOfLastOutdented = unwrappedOutdentResult.UnwrapLeftContent();
6089 middleContentOfLastOutdented =
6090 unwrappedOutdentResult.UnwrapMiddleContent();
6091 rightContentOfLastOutdented =
6092 unwrappedOutdentResult.UnwrapRightContent();
6093 indentedParentElement = nullptr;
6094 firstContentToBeOutdented = nullptr;
6095 lastContentToBeOutdented = nullptr;
6096 indentedParentIndentedWith = BlockIndentedWith::HTML;
6098 Result<EditorDOMPoint, nsresult> unwrapBlockquoteElementResult =
6099 RemoveBlockContainerWithTransaction(
6100 MOZ_KnownLive(*content->AsElement()));
6101 if (MOZ_UNLIKELY(unwrapBlockquoteElementResult.isErr())) {
6102 NS_WARNING("HTMLEditor::RemoveBlockContainerWithTransaction() failed");
6103 return unwrapBlockquoteElementResult.propagateErr();
6105 const EditorDOMPoint& pointToPutCaret =
6106 unwrapBlockquoteElementResult.inspect();
6107 if (AllowsTransactionsToChangeSelection() && pointToPutCaret.IsSet()) {
6108 nsresult rv = CollapseSelectionTo(pointToPutCaret);
6109 if (NS_FAILED(rv)) {
6110 NS_WARNING("EditorBase::CollapseSelectionTo() failed");
6111 return Err(rv);
6114 continue;
6117 // If we're using CSS and the node is a block element, check its start
6118 // margin whether it's indented with CSS.
6119 if (useCSS && HTMLEditUtils::IsBlockElement(
6120 content, BlockInlineCheck::UseHTMLDefaultStyle)) {
6121 nsStaticAtom& marginProperty =
6122 MarginPropertyAtomForIndent(MOZ_KnownLive(content));
6123 if (NS_WARN_IF(Destroyed())) {
6124 return Err(NS_ERROR_EDITOR_DESTROYED);
6126 nsAutoString value;
6127 DebugOnly<nsresult> rvIgnored =
6128 CSSEditUtils::GetSpecifiedProperty(content, marginProperty, value);
6129 if (NS_WARN_IF(Destroyed())) {
6130 return Err(NS_ERROR_EDITOR_DESTROYED);
6132 NS_WARNING_ASSERTION(
6133 NS_SUCCEEDED(rvIgnored),
6134 "CSSEditUtils::GetSpecifiedProperty() failed, but ignored");
6135 float startMargin = 0;
6136 RefPtr<nsAtom> unit;
6137 CSSEditUtils::ParseLength(value, &startMargin, getter_AddRefs(unit));
6138 // If indented with CSS, we should decrease the start margin.
6139 if (startMargin > 0) {
6140 const Result<EditorDOMPoint, nsresult> pointToPutCaretOrError =
6141 ChangeMarginStart(MOZ_KnownLive(*content->AsElement()),
6142 ChangeMargin::Decrease, aEditingHost);
6143 if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) {
6144 if (NS_WARN_IF(pointToPutCaretOrError.inspectErr() ==
6145 NS_ERROR_EDITOR_DESTROYED)) {
6146 return Err(NS_ERROR_EDITOR_DESTROYED);
6148 NS_WARNING(
6149 "HTMLEditor::ChangeMarginStart(ChangeMargin::Decrease) failed, "
6150 "but ignored");
6151 } else if (AllowsTransactionsToChangeSelection() &&
6152 pointToPutCaretOrError.inspect().IsSet()) {
6153 nsresult rv = CollapseSelectionTo(pointToPutCaretOrError.inspect());
6154 if (NS_FAILED(rv)) {
6155 NS_WARNING("EditorBase::CollapseSelectionTo() failed");
6156 return Err(rv);
6159 continue;
6163 // If it's a list item, we should treat as that it "indents" its children.
6164 if (HTMLEditUtils::IsListItem(content)) {
6165 // If it is a list item, that means we are not outdenting whole list.
6166 // XXX I don't understand this sentence... We may meet parent list
6167 // element, no?
6168 if (indentedParentElement) {
6169 Result<SplitRangeOffFromNodeResult, nsresult> outdentResult =
6170 OutdentPartOfBlock(*indentedParentElement,
6171 *firstContentToBeOutdented,
6172 *lastContentToBeOutdented,
6173 indentedParentIndentedWith, aEditingHost);
6174 if (MOZ_UNLIKELY(outdentResult.isErr())) {
6175 NS_WARNING("HTMLEditor::OutdentPartOfBlock() failed");
6176 return outdentResult;
6178 SplitRangeOffFromNodeResult unwrappedOutdentResult =
6179 outdentResult.unwrap();
6180 unwrappedOutdentResult.IgnoreCaretPointSuggestion();
6181 leftContentOfLastOutdented = unwrappedOutdentResult.UnwrapLeftContent();
6182 middleContentOfLastOutdented =
6183 unwrappedOutdentResult.UnwrapMiddleContent();
6184 rightContentOfLastOutdented =
6185 unwrappedOutdentResult.UnwrapRightContent();
6186 indentedParentElement = nullptr;
6187 firstContentToBeOutdented = nullptr;
6188 lastContentToBeOutdented = nullptr;
6189 indentedParentIndentedWith = BlockIndentedWith::HTML;
6191 // XXX `content` could become different element since
6192 // `OutdentPartOfBlock()` may run mutation event listeners.
6193 nsresult rv = LiftUpListItemElement(MOZ_KnownLive(*content->AsElement()),
6194 LiftUpFromAllParentListElements::No);
6195 if (NS_FAILED(rv)) {
6196 NS_WARNING(
6197 "HTMLEditor::LiftUpListItemElement(LiftUpFromAllParentListElements:"
6198 ":No) failed");
6199 return Err(rv);
6201 continue;
6204 // If we've found an ancestor block element which indents its children
6205 // and the current node is NOT a descendant of it, we should remove it to
6206 // outdent its children. Otherwise, i.e., current node is a descendant of
6207 // it, we meet new node which should be outdented when the indented parent
6208 // is removed.
6209 if (indentedParentElement) {
6210 if (EditorUtils::IsDescendantOf(*content, *indentedParentElement)) {
6211 // Extend the range to be outdented at removing the
6212 // indentedParentElement.
6213 lastContentToBeOutdented = content;
6214 continue;
6216 Result<SplitRangeOffFromNodeResult, nsresult> outdentResult =
6217 OutdentPartOfBlock(*indentedParentElement, *firstContentToBeOutdented,
6218 *lastContentToBeOutdented,
6219 indentedParentIndentedWith, aEditingHost);
6220 if (MOZ_UNLIKELY(outdentResult.isErr())) {
6221 NS_WARNING("HTMLEditor::OutdentPartOfBlock() failed");
6222 return outdentResult;
6224 SplitRangeOffFromNodeResult unwrappedOutdentResult =
6225 outdentResult.unwrap();
6226 unwrappedOutdentResult.IgnoreCaretPointSuggestion();
6227 leftContentOfLastOutdented = unwrappedOutdentResult.UnwrapLeftContent();
6228 middleContentOfLastOutdented =
6229 unwrappedOutdentResult.UnwrapMiddleContent();
6230 rightContentOfLastOutdented = unwrappedOutdentResult.UnwrapRightContent();
6231 indentedParentElement = nullptr;
6232 firstContentToBeOutdented = nullptr;
6233 lastContentToBeOutdented = nullptr;
6234 // curBlockIndentedWith = HTMLEditor::BlockIndentedWith::HTML;
6236 // Then, we need to look for next indentedParentElement.
6239 indentedParentIndentedWith = BlockIndentedWith::HTML;
6240 for (nsCOMPtr<nsIContent> parentContent = content->GetParent();
6241 parentContent && !parentContent->IsHTMLElement(nsGkAtoms::body) &&
6242 parentContent != &aEditingHost &&
6243 (parentContent->IsHTMLElement(nsGkAtoms::table) ||
6244 !HTMLEditUtils::IsAnyTableElement(parentContent));
6245 parentContent = parentContent->GetParent()) {
6246 if (MOZ_UNLIKELY(!HTMLEditUtils::IsRemovableNode(*parentContent))) {
6247 continue;
6249 // If we reach a `<blockquote>` ancestor, it should be split at next
6250 // time at least for outdenting current node.
6251 if (parentContent->IsHTMLElement(nsGkAtoms::blockquote)) {
6252 indentedParentElement = parentContent->AsElement();
6253 firstContentToBeOutdented = content;
6254 lastContentToBeOutdented = content;
6255 break;
6258 if (!useCSS) {
6259 continue;
6262 nsCOMPtr<nsINode> grandParentNode = parentContent->GetParentNode();
6263 nsStaticAtom& marginProperty =
6264 MarginPropertyAtomForIndent(MOZ_KnownLive(content));
6265 if (NS_WARN_IF(Destroyed())) {
6266 return Err(NS_ERROR_EDITOR_DESTROYED);
6268 if (NS_WARN_IF(grandParentNode != parentContent->GetParentNode())) {
6269 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
6271 nsAutoString value;
6272 DebugOnly<nsresult> rvIgnored = CSSEditUtils::GetSpecifiedProperty(
6273 *parentContent, marginProperty, value);
6274 if (NS_WARN_IF(Destroyed())) {
6275 return Err(NS_ERROR_EDITOR_DESTROYED);
6277 NS_WARNING_ASSERTION(
6278 NS_SUCCEEDED(rvIgnored),
6279 "CSSEditUtils::GetSpecifiedProperty() failed, but ignored");
6280 // XXX Now, editing host may become different element. If so, shouldn't
6281 // we stop this handling?
6282 float startMargin;
6283 RefPtr<nsAtom> unit;
6284 CSSEditUtils::ParseLength(value, &startMargin, getter_AddRefs(unit));
6285 // If we reach a block element which indents its children with start
6286 // margin, we should remove it at next time.
6287 if (startMargin > 0 &&
6288 !(HTMLEditUtils::IsAnyListElement(atContent.GetContainer()) &&
6289 HTMLEditUtils::IsAnyListElement(content))) {
6290 indentedParentElement = parentContent->AsElement();
6291 firstContentToBeOutdented = content;
6292 lastContentToBeOutdented = content;
6293 indentedParentIndentedWith = BlockIndentedWith::CSS;
6294 break;
6298 if (indentedParentElement) {
6299 continue;
6302 // If we don't have any block elements which indents current node and
6303 // both current node and its parent are list element, remove current
6304 // node to move all its children to the parent list.
6305 // XXX This is buggy. When both lists' item types are different,
6306 // we create invalid tree. E.g., `<ul>` may have `<dd>` as its
6307 // list item element.
6308 if (HTMLEditUtils::IsAnyListElement(atContent.GetContainer())) {
6309 if (!HTMLEditUtils::IsAnyListElement(content)) {
6310 continue;
6312 // Just unwrap this sublist
6313 Result<EditorDOMPoint, nsresult> unwrapSubListElementResult =
6314 RemoveBlockContainerWithTransaction(
6315 MOZ_KnownLive(*content->AsElement()));
6316 if (MOZ_UNLIKELY(unwrapSubListElementResult.isErr())) {
6317 NS_WARNING("HTMLEditor::RemoveBlockContainerWithTransaction() failed");
6318 return unwrapSubListElementResult.propagateErr();
6320 const EditorDOMPoint& pointToPutCaret =
6321 unwrapSubListElementResult.inspect();
6322 if (!AllowsTransactionsToChangeSelection() || !pointToPutCaret.IsSet()) {
6323 continue;
6325 nsresult rv = CollapseSelectionTo(pointToPutCaret);
6326 if (NS_FAILED(rv)) {
6327 NS_WARNING("EditorBase::CollapseSelectionTo() failed");
6328 return Err(rv);
6330 continue;
6333 // If current content is a list element but its parent is not a list
6334 // element, move children to where it is and remove it from the tree.
6335 if (HTMLEditUtils::IsAnyListElement(content)) {
6336 // XXX If mutation event listener appends new children forever, this
6337 // becomes an infinite loop so that we should set limitation from
6338 // first child count.
6339 for (nsCOMPtr<nsIContent> lastChildContent = content->GetLastChild();
6340 lastChildContent; lastChildContent = content->GetLastChild()) {
6341 if (HTMLEditUtils::IsListItem(lastChildContent)) {
6342 nsresult rv = LiftUpListItemElement(
6343 MOZ_KnownLive(*lastChildContent->AsElement()),
6344 LiftUpFromAllParentListElements::No);
6345 if (NS_FAILED(rv)) {
6346 NS_WARNING(
6347 "HTMLEditor::LiftUpListItemElement("
6348 "LiftUpFromAllParentListElements::No) failed");
6349 return Err(rv);
6351 continue;
6354 if (HTMLEditUtils::IsAnyListElement(lastChildContent)) {
6355 // We have an embedded list, so move it out from under the parent
6356 // list. Be sure to put it after the parent list because this
6357 // loop iterates backwards through the parent's list of children.
6358 EditorDOMPoint afterCurrentList(EditorDOMPoint::After(atContent));
6359 NS_WARNING_ASSERTION(
6360 afterCurrentList.IsSet(),
6361 "Failed to set it to after current list element");
6362 Result<MoveNodeResult, nsresult> moveListElementResult =
6363 MoveNodeWithTransaction(*lastChildContent, afterCurrentList);
6364 if (MOZ_UNLIKELY(moveListElementResult.isErr())) {
6365 NS_WARNING("HTMLEditor::MoveNodeWithTransaction() failed");
6366 return moveListElementResult.propagateErr();
6368 nsresult rv = moveListElementResult.inspect().SuggestCaretPointTo(
6369 *this, {SuggestCaret::OnlyIfHasSuggestion,
6370 SuggestCaret::OnlyIfTransactionsAllowedToDoIt,
6371 SuggestCaret::AndIgnoreTrivialError});
6372 if (NS_FAILED(rv)) {
6373 NS_WARNING("MoveNodeResult::SuggestCaretPointTo() failed");
6374 return Err(rv);
6376 NS_WARNING_ASSERTION(
6377 rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
6378 "MoveNodeResult::SuggestCaretPointTo() failed, but ignored");
6379 continue;
6382 // Delete any non-list items for now
6383 // XXX Chrome moves it from the list element. We should follow it.
6384 nsresult rv = DeleteNodeWithTransaction(*lastChildContent);
6385 if (NS_FAILED(rv)) {
6386 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
6387 return Err(rv);
6390 // Delete the now-empty list
6391 Result<EditorDOMPoint, nsresult> unwrapListElementResult =
6392 RemoveBlockContainerWithTransaction(
6393 MOZ_KnownLive(*content->AsElement()));
6394 if (MOZ_UNLIKELY(unwrapListElementResult.isErr())) {
6395 NS_WARNING("HTMLEditor::RemoveBlockContainerWithTransaction() failed");
6396 return unwrapListElementResult.propagateErr();
6398 const EditorDOMPoint& pointToPutCaret = unwrapListElementResult.inspect();
6399 if (!AllowsTransactionsToChangeSelection() || !pointToPutCaret.IsSet()) {
6400 continue;
6402 nsresult rv = CollapseSelectionTo(pointToPutCaret);
6403 if (NS_FAILED(rv)) {
6404 NS_WARNING("EditorBase::CollapseSelectionTo() failed");
6405 return Err(rv);
6407 continue;
6410 if (useCSS) {
6411 if (RefPtr<Element> element = content->GetAsElementOrParentElement()) {
6412 const Result<EditorDOMPoint, nsresult> pointToPutCaretOrError =
6413 ChangeMarginStart(*element, ChangeMargin::Decrease, aEditingHost);
6414 if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) {
6415 if (NS_WARN_IF(pointToPutCaretOrError.inspectErr() ==
6416 NS_ERROR_EDITOR_DESTROYED)) {
6417 return Err(NS_ERROR_EDITOR_DESTROYED);
6419 NS_WARNING(
6420 "HTMLEditor::ChangeMarginStart(ChangeMargin::Decrease) failed, "
6421 "but ignored");
6422 } else if (AllowsTransactionsToChangeSelection() &&
6423 pointToPutCaretOrError.inspect().IsSet()) {
6424 nsresult rv = CollapseSelectionTo(pointToPutCaretOrError.inspect());
6425 if (NS_FAILED(rv)) {
6426 NS_WARNING("EditorBase::CollapseSelectionTo() failed");
6427 return Err(rv);
6431 continue;
6435 if (!indentedParentElement) {
6436 return SplitRangeOffFromNodeResult(leftContentOfLastOutdented,
6437 middleContentOfLastOutdented,
6438 rightContentOfLastOutdented);
6441 // We have a <blockquote> we haven't finished handling.
6442 Result<SplitRangeOffFromNodeResult, nsresult> outdentResult =
6443 OutdentPartOfBlock(*indentedParentElement, *firstContentToBeOutdented,
6444 *lastContentToBeOutdented, indentedParentIndentedWith,
6445 aEditingHost);
6446 if (MOZ_UNLIKELY(outdentResult.isErr())) {
6447 NS_WARNING("HTMLEditor::OutdentPartOfBlock() failed");
6448 return outdentResult;
6450 // We will restore selection soon. Therefore, callers do not need to restore
6451 // the selection.
6452 SplitRangeOffFromNodeResult unwrappedOutdentResult = outdentResult.unwrap();
6453 unwrappedOutdentResult.ForgetCaretPointSuggestion();
6454 return unwrappedOutdentResult;
6457 Result<SplitRangeOffFromNodeResult, nsresult>
6458 HTMLEditor::RemoveBlockContainerElementWithTransactionBetween(
6459 Element& aBlockContainerElement, nsIContent& aStartOfRange,
6460 nsIContent& aEndOfRange, BlockInlineCheck aBlockInlineCheck) {
6461 MOZ_ASSERT(IsEditActionDataAvailable());
6463 EditorDOMPoint pointToPutCaret;
6464 Result<SplitRangeOffFromNodeResult, nsresult> splitResult =
6465 SplitRangeOffFromElement(aBlockContainerElement, aStartOfRange,
6466 aEndOfRange);
6467 if (MOZ_UNLIKELY(splitResult.isErr())) {
6468 if (splitResult.inspectErr() == NS_ERROR_EDITOR_DESTROYED) {
6469 NS_WARNING("HTMLEditor::SplitRangeOffFromElement() failed");
6470 return splitResult;
6472 NS_WARNING(
6473 "HTMLEditor::SplitRangeOffFromElement() failed, but might be ignored");
6474 return SplitRangeOffFromNodeResult(nullptr, nullptr, nullptr);
6476 SplitRangeOffFromNodeResult unwrappedSplitResult = splitResult.unwrap();
6477 unwrappedSplitResult.MoveCaretPointTo(pointToPutCaret,
6478 {SuggestCaret::OnlyIfHasSuggestion});
6480 // Even if either split aBlockContainerElement or did not split it, we should
6481 // unwrap the right most element which is split from aBlockContainerElement
6482 // (or aBlockContainerElement itself if it was not split without errors).
6483 Element* rightmostElement =
6484 unwrappedSplitResult.GetRightmostContentAs<Element>();
6485 MOZ_ASSERT(rightmostElement);
6486 if (NS_WARN_IF(!rightmostElement)) {
6487 return Err(NS_ERROR_FAILURE);
6491 // MOZ_KnownLive(rightmostElement) because it's grabbed by
6492 // unwrappedSplitResult.
6493 Result<EditorDOMPoint, nsresult> unwrapBlockElementResult =
6494 RemoveBlockContainerWithTransaction(MOZ_KnownLive(*rightmostElement));
6495 if (MOZ_UNLIKELY(unwrapBlockElementResult.isErr())) {
6496 NS_WARNING("HTMLEditor::RemoveBlockContainerWithTransaction() failed");
6497 return unwrapBlockElementResult.propagateErr();
6499 if (unwrapBlockElementResult.inspect().IsSet()) {
6500 pointToPutCaret = unwrapBlockElementResult.unwrap();
6504 return SplitRangeOffFromNodeResult(
6505 unwrappedSplitResult.GetLeftContent(), nullptr,
6506 unwrappedSplitResult.GetRightContent(), std::move(pointToPutCaret));
6509 Result<SplitRangeOffFromNodeResult, nsresult>
6510 HTMLEditor::SplitRangeOffFromElement(Element& aElementToSplit,
6511 nsIContent& aStartOfMiddleElement,
6512 nsIContent& aEndOfMiddleElement) {
6513 MOZ_ASSERT(IsEditActionDataAvailable());
6515 // aStartOfMiddleElement and aEndOfMiddleElement must be exclusive
6516 // descendants of aElementToSplit.
6517 MOZ_ASSERT(
6518 EditorUtils::IsDescendantOf(aStartOfMiddleElement, aElementToSplit));
6519 MOZ_ASSERT(EditorUtils::IsDescendantOf(aEndOfMiddleElement, aElementToSplit));
6521 EditorDOMPoint pointToPutCaret;
6522 // Split at the start.
6523 Result<SplitNodeResult, nsresult> splitAtStartResult =
6524 SplitNodeDeepWithTransaction(aElementToSplit,
6525 EditorDOMPoint(&aStartOfMiddleElement),
6526 SplitAtEdges::eDoNotCreateEmptyContainer);
6527 if (MOZ_UNLIKELY(splitAtStartResult.isErr())) {
6528 if (splitAtStartResult.inspectErr() == NS_ERROR_EDITOR_DESTROYED) {
6529 NS_WARNING("HTMLEditor::SplitNodeDeepWithTransaction() failed (at left)");
6530 return Err(NS_ERROR_EDITOR_DESTROYED);
6532 NS_WARNING(
6533 "HTMLEditor::SplitNodeDeepWithTransaction(SplitAtEdges::"
6534 "eDoNotCreateEmptyContainer) at start of middle element failed");
6535 } else {
6536 splitAtStartResult.inspect().CopyCaretPointTo(
6537 pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
6540 // Split at after the end
6541 auto atAfterEnd = EditorDOMPoint::After(aEndOfMiddleElement);
6542 Element* rightElement =
6543 splitAtStartResult.isOk() && splitAtStartResult.inspect().DidSplit()
6544 ? splitAtStartResult.inspect().GetNextContentAs<Element>()
6545 : &aElementToSplit;
6546 // MOZ_KnownLive(rightElement) because it's grabbed by splitAtStartResult or
6547 // aElementToSplit whose lifetime is guaranteed by the caller.
6548 Result<SplitNodeResult, nsresult> splitAtEndResult =
6549 SplitNodeDeepWithTransaction(MOZ_KnownLive(*rightElement), atAfterEnd,
6550 SplitAtEdges::eDoNotCreateEmptyContainer);
6551 if (MOZ_UNLIKELY(splitAtEndResult.isErr())) {
6552 if (splitAtEndResult.inspectErr() == NS_ERROR_EDITOR_DESTROYED) {
6553 NS_WARNING(
6554 "HTMLEditor::SplitNodeDeepWithTransaction() failed (at right)");
6555 return Err(NS_ERROR_EDITOR_DESTROYED);
6557 NS_WARNING(
6558 "HTMLEditor::SplitNodeDeepWithTransaction(SplitAtEdges::"
6559 "eDoNotCreateEmptyContainer) after end of middle element failed");
6560 } else {
6561 splitAtEndResult.inspect().CopyCaretPointTo(
6562 pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
6565 if (splitAtStartResult.isOk() && splitAtStartResult.inspect().DidSplit() &&
6566 splitAtEndResult.isOk() && splitAtEndResult.inspect().DidSplit()) {
6567 // Note that the middle node can be computed only with the latter split
6568 // result.
6569 return SplitRangeOffFromNodeResult(
6570 splitAtStartResult.inspect().GetPreviousContent(),
6571 splitAtEndResult.inspect().GetPreviousContent(),
6572 splitAtEndResult.inspect().GetNextContent(),
6573 std::move(pointToPutCaret));
6575 if (splitAtStartResult.isOk() && splitAtStartResult.inspect().DidSplit()) {
6576 return SplitRangeOffFromNodeResult(
6577 splitAtStartResult.inspect().GetPreviousContent(),
6578 splitAtStartResult.inspect().GetNextContent(), nullptr,
6579 std::move(pointToPutCaret));
6581 if (splitAtEndResult.isOk() && splitAtEndResult.inspect().DidSplit()) {
6582 return SplitRangeOffFromNodeResult(
6583 nullptr, splitAtEndResult.inspect().GetPreviousContent(),
6584 splitAtEndResult.inspect().GetNextContent(),
6585 std::move(pointToPutCaret));
6587 return SplitRangeOffFromNodeResult(nullptr, &aElementToSplit, nullptr,
6588 std::move(pointToPutCaret));
6591 Result<SplitRangeOffFromNodeResult, nsresult> HTMLEditor::OutdentPartOfBlock(
6592 Element& aBlockElement, nsIContent& aStartOfOutdent,
6593 nsIContent& aEndOfOutdent, BlockIndentedWith aBlockIndentedWith,
6594 const Element& aEditingHost) {
6595 MOZ_ASSERT(IsEditActionDataAvailable());
6597 Result<SplitRangeOffFromNodeResult, nsresult> splitResult =
6598 SplitRangeOffFromElement(aBlockElement, aStartOfOutdent, aEndOfOutdent);
6599 if (MOZ_UNLIKELY(splitResult.isErr())) {
6600 NS_WARNING("HTMLEditor::SplitRangeOffFromElement() failed");
6601 return splitResult;
6604 SplitRangeOffFromNodeResult unwrappedSplitResult = splitResult.unwrap();
6605 Element* middleElement = unwrappedSplitResult.GetMiddleContentAs<Element>();
6606 if (MOZ_UNLIKELY(!middleElement)) {
6607 NS_WARNING(
6608 "HTMLEditor::SplitRangeOffFromElement() didn't return middle content");
6609 unwrappedSplitResult.IgnoreCaretPointSuggestion();
6610 return Err(NS_ERROR_FAILURE);
6612 if (NS_WARN_IF(!HTMLEditUtils::IsRemovableNode(*middleElement))) {
6613 unwrappedSplitResult.IgnoreCaretPointSuggestion();
6614 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
6617 nsresult rv = unwrappedSplitResult.SuggestCaretPointTo(
6618 *this, {SuggestCaret::OnlyIfHasSuggestion,
6619 SuggestCaret::OnlyIfTransactionsAllowedToDoIt,
6620 SuggestCaret::AndIgnoreTrivialError});
6621 if (NS_FAILED(rv)) {
6622 NS_WARNING("SplitRangeOffFromNodeResult::SuggestCaretPointTo() failed");
6623 return Err(rv);
6625 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
6626 "SplitRangeOffFromNodeResult::SuggestCaretPointTo() "
6627 "failed, but ignored");
6629 if (aBlockIndentedWith == BlockIndentedWith::HTML) {
6630 // MOZ_KnownLive(middleElement) because of grabbed by unwrappedSplitResult.
6631 Result<EditorDOMPoint, nsresult> unwrapBlockElementResult =
6632 RemoveBlockContainerWithTransaction(MOZ_KnownLive(*middleElement));
6633 if (MOZ_UNLIKELY(unwrapBlockElementResult.isErr())) {
6634 NS_WARNING("HTMLEditor::RemoveBlockContainerWithTransaction() failed");
6635 return unwrapBlockElementResult.propagateErr();
6637 const EditorDOMPoint& pointToPutCaret = unwrapBlockElementResult.inspect();
6638 if (AllowsTransactionsToChangeSelection() && pointToPutCaret.IsSet()) {
6639 nsresult rv = CollapseSelectionTo(pointToPutCaret);
6640 if (NS_FAILED(rv)) {
6641 NS_WARNING("EditorBase::CollapseSelectionTo() failed");
6642 return Err(rv);
6645 return SplitRangeOffFromNodeResult(unwrappedSplitResult.GetLeftContent(),
6646 nullptr,
6647 unwrappedSplitResult.GetRightContent());
6650 // MOZ_KnownLive(middleElement) because of grabbed by unwrappedSplitResult.
6651 Result<EditorDOMPoint, nsresult> pointToPutCaretOrError = ChangeMarginStart(
6652 MOZ_KnownLive(*middleElement), ChangeMargin::Decrease, aEditingHost);
6653 if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) {
6654 NS_WARNING("HTMLEditor::ChangeMarginStart(ChangeMargin::Decrease) failed");
6655 return pointToPutCaretOrError.propagateErr();
6657 if (AllowsTransactionsToChangeSelection() &&
6658 pointToPutCaretOrError.inspect().IsSet()) {
6659 nsresult rv = CollapseSelectionTo(pointToPutCaretOrError.inspect());
6660 if (NS_FAILED(rv)) {
6661 NS_WARNING("EditorBase::CollapseSelectionTo() failed");
6662 return Err(rv);
6665 return unwrappedSplitResult;
6668 Result<CreateElementResult, nsresult> HTMLEditor::ChangeListElementType(
6669 Element& aListElement, nsAtom& aNewListTag, nsAtom& aNewListItemTag) {
6670 MOZ_ASSERT(IsEditActionDataAvailable());
6672 EditorDOMPoint pointToPutCaret;
6674 AutoTArray<OwningNonNull<nsIContent>, 32> listElementChildren;
6675 HTMLEditUtils::CollectAllChildren(aListElement, listElementChildren);
6677 for (const OwningNonNull<nsIContent>& childContent : listElementChildren) {
6678 if (!childContent->IsElement()) {
6679 continue;
6681 Element* childElement = childContent->AsElement();
6682 if (HTMLEditUtils::IsListItem(childElement) &&
6683 !childContent->IsHTMLElement(&aNewListItemTag)) {
6684 // MOZ_KnownLive(childElement) because its lifetime is guaranteed by
6685 // listElementChildren.
6686 Result<CreateElementResult, nsresult>
6687 replaceWithNewListItemElementResult =
6688 ReplaceContainerAndCloneAttributesWithTransaction(
6689 MOZ_KnownLive(*childElement), aNewListItemTag);
6690 if (MOZ_UNLIKELY(replaceWithNewListItemElementResult.isErr())) {
6691 NS_WARNING(
6692 "HTMLEditor::ReplaceContainerAndCloneAttributesWithTransaction() "
6693 "failed");
6694 return replaceWithNewListItemElementResult;
6696 CreateElementResult unwrappedReplaceWithNewListItemElementResult =
6697 replaceWithNewListItemElementResult.unwrap();
6698 unwrappedReplaceWithNewListItemElementResult.MoveCaretPointTo(
6699 pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
6700 continue;
6702 if (HTMLEditUtils::IsAnyListElement(childElement) &&
6703 !childElement->IsHTMLElement(&aNewListTag)) {
6704 // XXX List elements shouldn't have other list elements as their
6705 // child. Why do we handle such invalid tree?
6706 // -> Maybe, for bug 525888.
6707 // MOZ_KnownLive(childElement) because its lifetime is guaranteed by
6708 // listElementChildren.
6709 Result<CreateElementResult, nsresult> convertListTypeResult =
6710 ChangeListElementType(MOZ_KnownLive(*childElement), aNewListTag,
6711 aNewListItemTag);
6712 if (MOZ_UNLIKELY(convertListTypeResult.isErr())) {
6713 NS_WARNING("HTMLEditor::ChangeListElementType() failed");
6714 return convertListTypeResult;
6716 CreateElementResult unwrappedConvertListTypeResult =
6717 convertListTypeResult.unwrap();
6718 unwrappedConvertListTypeResult.MoveCaretPointTo(
6719 pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
6720 continue;
6724 if (aListElement.IsHTMLElement(&aNewListTag)) {
6725 return CreateElementResult(&aListElement, std::move(pointToPutCaret));
6728 // XXX If we replace the list element, shouldn't we create it first and then,
6729 // move children into it before inserting the new list element into the
6730 // DOM tree? Then, we could reduce the cost of dispatching DOM mutation
6731 // events.
6732 Result<CreateElementResult, nsresult> replaceWithNewListElementResult =
6733 ReplaceContainerWithTransaction(aListElement, aNewListTag);
6734 if (MOZ_UNLIKELY(replaceWithNewListElementResult.isErr())) {
6735 NS_WARNING("HTMLEditor::ReplaceContainerWithTransaction() failed");
6736 return replaceWithNewListElementResult;
6738 CreateElementResult unwrappedReplaceWithNewListElementResult =
6739 replaceWithNewListElementResult.unwrap();
6740 unwrappedReplaceWithNewListElementResult.MoveCaretPointTo(
6741 pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
6742 return CreateElementResult(
6743 unwrappedReplaceWithNewListElementResult.UnwrapNewNode(),
6744 std::move(pointToPutCaret));
6747 Result<EditorDOMPoint, nsresult> HTMLEditor::CreateStyleForInsertText(
6748 const EditorDOMPoint& aPointToInsertText, const Element& aEditingHost) {
6749 MOZ_ASSERT(IsEditActionDataAvailable());
6750 MOZ_ASSERT(aPointToInsertText.IsSetAndValid());
6751 MOZ_ASSERT(mPendingStylesToApplyToNewContent);
6753 const RefPtr<Element> documentRootElement = GetDocument()->GetRootElement();
6754 if (NS_WARN_IF(!documentRootElement)) {
6755 return Err(NS_ERROR_FAILURE);
6758 // process clearing any styles first
6759 UniquePtr<PendingStyle> pendingStyle =
6760 mPendingStylesToApplyToNewContent->TakeClearingStyle();
6762 EditorDOMPoint pointToPutCaret(aPointToInsertText);
6764 // Transactions may set selection, but we will set selection if necessary.
6765 AutoTransactionsConserveSelection dontChangeMySelection(*this);
6767 while (pendingStyle &&
6768 pointToPutCaret.GetContainer() != documentRootElement) {
6769 // MOZ_KnownLive because we own pendingStyle which guarantees the lifetime
6770 // of its members.
6771 Result<EditorDOMPoint, nsresult> pointToPutCaretOrError =
6772 ClearStyleAt(pointToPutCaret, pendingStyle->ToInlineStyle(),
6773 pendingStyle->GetSpecifiedStyle());
6774 if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) {
6775 NS_WARNING("HTMLEditor::ClearStyleAt() failed");
6776 return pointToPutCaretOrError;
6778 pointToPutCaret = pointToPutCaretOrError.unwrap();
6779 if (NS_WARN_IF(!pointToPutCaret.IsSetAndValid())) {
6780 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
6782 pendingStyle = mPendingStylesToApplyToNewContent->TakeClearingStyle();
6786 // then process setting any styles
6787 const int32_t relFontSize =
6788 mPendingStylesToApplyToNewContent->TakeRelativeFontSize();
6789 AutoTArray<EditorInlineStyleAndValue, 32> stylesToSet;
6790 mPendingStylesToApplyToNewContent->TakeAllPreservedStyles(stylesToSet);
6791 if (stylesToSet.IsEmpty() && !relFontSize) {
6792 return pointToPutCaret;
6795 // We're in chrome, e.g., the email composer of Thunderbird, and there is
6796 // relative font size changes, we need to keep using legacy path until we port
6797 // IncrementOrDecrementFontSizeAsSubAction() to work with
6798 // AutoInlineStyleSetter.
6799 if (relFontSize) {
6800 // we have at least one style to add; make a new text node to insert style
6801 // nodes above.
6802 EditorDOMPoint pointToInsertTextNode(pointToPutCaret);
6803 if (pointToInsertTextNode.IsInTextNode()) {
6804 // if we are in a text node, split it
6805 Result<SplitNodeResult, nsresult> splitTextNodeResult =
6806 SplitNodeDeepWithTransaction(
6807 MOZ_KnownLive(*pointToInsertTextNode.ContainerAs<Text>()),
6808 pointToInsertTextNode,
6809 SplitAtEdges::eAllowToCreateEmptyContainer);
6810 if (MOZ_UNLIKELY(splitTextNodeResult.isErr())) {
6811 NS_WARNING(
6812 "HTMLEditor::SplitNodeDeepWithTransaction(SplitAtEdges::"
6813 "eAllowToCreateEmptyContainer) failed");
6814 return splitTextNodeResult.propagateErr();
6816 SplitNodeResult unwrappedSplitTextNodeResult =
6817 splitTextNodeResult.unwrap();
6818 unwrappedSplitTextNodeResult.MoveCaretPointTo(
6819 pointToPutCaret, *this,
6820 {SuggestCaret::OnlyIfHasSuggestion,
6821 SuggestCaret::OnlyIfTransactionsAllowedToDoIt});
6822 pointToInsertTextNode =
6823 unwrappedSplitTextNodeResult.AtSplitPoint<EditorDOMPoint>();
6825 if (!pointToInsertTextNode.IsInContentNode() ||
6826 !HTMLEditUtils::IsContainerNode(
6827 *pointToInsertTextNode.ContainerAs<nsIContent>())) {
6828 return pointToPutCaret;
6830 RefPtr<Text> newEmptyTextNode = CreateTextNode(u""_ns);
6831 if (!newEmptyTextNode) {
6832 NS_WARNING("EditorBase::CreateTextNode() failed");
6833 return Err(NS_ERROR_FAILURE);
6835 Result<CreateTextResult, nsresult> insertNewTextNodeResult =
6836 InsertNodeWithTransaction<Text>(*newEmptyTextNode,
6837 pointToInsertTextNode);
6838 if (MOZ_UNLIKELY(insertNewTextNodeResult.isErr())) {
6839 NS_WARNING("EditorBase::InsertNodeWithTransaction() failed");
6840 return insertNewTextNodeResult.propagateErr();
6842 insertNewTextNodeResult.inspect().IgnoreCaretPointSuggestion();
6843 pointToPutCaret.Set(newEmptyTextNode, 0u);
6845 // FIXME: If the stylesToSet have background-color style, it may
6846 // be applied shorter because outer <span> element height is not
6847 // computed with inner element's height.
6848 HTMLEditor::FontSize incrementOrDecrement =
6849 relFontSize > 0 ? HTMLEditor::FontSize::incr
6850 : HTMLEditor::FontSize::decr;
6851 for ([[maybe_unused]] uint32_t j : IntegerRange(Abs(relFontSize))) {
6852 Result<CreateElementResult, nsresult> wrapTextInBigOrSmallElementResult =
6853 SetFontSizeOnTextNode(*newEmptyTextNode, 0, UINT32_MAX,
6854 incrementOrDecrement);
6855 if (MOZ_UNLIKELY(wrapTextInBigOrSmallElementResult.isErr())) {
6856 NS_WARNING("HTMLEditor::SetFontSizeOnTextNode() failed");
6857 return wrapTextInBigOrSmallElementResult.propagateErr();
6859 // We don't need to update here because we'll suggest caret position
6860 // which is computed above.
6861 MOZ_ASSERT(pointToPutCaret.IsSet());
6862 wrapTextInBigOrSmallElementResult.inspect().IgnoreCaretPointSuggestion();
6865 for (const EditorInlineStyleAndValue& styleToSet : stylesToSet) {
6866 AutoInlineStyleSetter inlineStyleSetter(styleToSet);
6867 // MOZ_KnownLive(...ContainerAs<nsIContent>()) because pointToPutCaret
6868 // grabs the result.
6869 Result<CaretPoint, nsresult> setStyleResult =
6870 inlineStyleSetter.ApplyStyleToNodeOrChildrenAndRemoveNestedSameStyle(
6871 *this, MOZ_KnownLive(*pointToPutCaret.ContainerAs<nsIContent>()));
6872 if (MOZ_UNLIKELY(setStyleResult.isErr())) {
6873 NS_WARNING("HTMLEditor::SetInlinePropertyOnNode() failed");
6874 return setStyleResult.propagateErr();
6876 // We don't need to update here because we'll suggest caret position which
6877 // is computed above.
6878 MOZ_ASSERT(pointToPutCaret.IsSet());
6879 setStyleResult.unwrap().IgnoreCaretPointSuggestion();
6881 return pointToPutCaret;
6884 // If we have preserved commands except relative font style changes, we can
6885 // use inline style setting code which reuse ancestors better.
6886 AutoRangeArray ranges(pointToPutCaret);
6887 if (MOZ_UNLIKELY(ranges.Ranges().IsEmpty())) {
6888 NS_WARNING("AutoRangeArray::AutoRangeArray() failed");
6889 return Err(NS_ERROR_FAILURE);
6891 nsresult rv =
6892 SetInlinePropertiesAroundRanges(ranges, stylesToSet, aEditingHost);
6893 if (NS_FAILED(rv)) {
6894 NS_WARNING("HTMLEditor::SetInlinePropertiesAroundRanges() failed");
6895 return Err(rv);
6897 if (NS_WARN_IF(ranges.Ranges().IsEmpty())) {
6898 return Err(NS_ERROR_FAILURE);
6900 // Now `ranges` selects new styled contents and the range may not be
6901 // collapsed. We should use the deepest editable start point of the range
6902 // to insert text.
6903 nsINode* container = ranges.FirstRangeRef()->GetStartContainer();
6904 if (MOZ_UNLIKELY(!container->IsContent())) {
6905 container = ranges.FirstRangeRef()->GetChildAtStartOffset();
6906 if (MOZ_UNLIKELY(!container)) {
6907 NS_WARNING("How did we get lost insertion point?");
6908 return Err(NS_ERROR_FAILURE);
6911 pointToPutCaret =
6912 HTMLEditUtils::GetDeepestEditableStartPointOf<EditorDOMPoint>(
6913 *container->AsContent());
6914 if (NS_WARN_IF(!pointToPutCaret.IsSet())) {
6915 return Err(NS_ERROR_FAILURE);
6917 return pointToPutCaret;
6920 Result<EditActionResult, nsresult> HTMLEditor::AlignAsSubAction(
6921 const nsAString& aAlignType, const Element& aEditingHost) {
6922 MOZ_ASSERT(IsEditActionDataAvailable());
6924 AutoPlaceholderBatch treatAsOneTransaction(
6925 *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
6926 IgnoredErrorResult ignoredError;
6927 AutoEditSubActionNotifier startToHandleEditSubAction(
6928 *this, EditSubAction::eSetOrClearAlignment, nsIEditor::eNext,
6929 ignoredError);
6930 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
6931 return Err(ignoredError.StealNSResult());
6933 NS_WARNING_ASSERTION(
6934 !ignoredError.Failed(),
6935 "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
6938 Result<EditActionResult, nsresult> result = CanHandleHTMLEditSubAction();
6939 if (MOZ_UNLIKELY(result.isErr())) {
6940 NS_WARNING("HTMLEditor::CanHandleHTMLEditSubAction() failed");
6941 return result;
6943 if (result.inspect().Canceled()) {
6944 return result;
6948 if (MOZ_UNLIKELY(IsSelectionRangeContainerNotContent())) {
6949 NS_WARNING("Some selection containers are not content node, but ignored");
6950 return EditActionResult::IgnoredResult();
6953 nsresult rv = EnsureNoPaddingBRElementForEmptyEditor();
6954 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
6955 return Err(NS_ERROR_EDITOR_DESTROYED);
6957 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
6958 "EditorBase::EnsureNoPaddingBRElementForEmptyEditor() "
6959 "failed, but ignored");
6961 if (MOZ_UNLIKELY(IsSelectionRangeContainerNotContent())) {
6962 NS_WARNING("Mutation event listener might have changed the selection");
6963 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
6966 if (NS_SUCCEEDED(rv) && SelectionRef().IsCollapsed()) {
6967 nsresult rv = EnsureCaretNotAfterInvisibleBRElement();
6968 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
6969 return Err(NS_ERROR_EDITOR_DESTROYED);
6971 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
6972 "HTMLEditor::EnsureCaretNotAfterInvisibleBRElement() "
6973 "failed, but ignored");
6974 if (NS_SUCCEEDED(rv)) {
6975 nsresult rv = PrepareInlineStylesForCaret();
6976 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
6977 return Err(NS_ERROR_EDITOR_DESTROYED);
6979 NS_WARNING_ASSERTION(
6980 NS_SUCCEEDED(rv),
6981 "HTMLEditor::PrepareInlineStylesForCaret() failed, but ignored");
6985 AutoRangeArray selectionRanges(SelectionRef());
6987 // XXX Why do we do this only when there is only one selection range?
6988 if (!selectionRanges.IsCollapsed() &&
6989 selectionRanges.Ranges().Length() == 1u) {
6990 Result<EditorRawDOMRange, nsresult> extendedRange =
6991 GetRangeExtendedToHardLineEdgesForBlockEditAction(
6992 selectionRanges.FirstRangeRef(), aEditingHost);
6993 if (MOZ_UNLIKELY(extendedRange.isErr())) {
6994 NS_WARNING(
6995 "HTMLEditor::GetRangeExtendedToHardLineEdgesForBlockEditAction() "
6996 "failed");
6997 return extendedRange.propagateErr();
6999 // Note that end point may be prior to start point. So, we
7000 // cannot use etStartAndEnd() here.
7001 nsresult rv = selectionRanges.SetBaseAndExtent(
7002 extendedRange.inspect().StartRef(), extendedRange.inspect().EndRef());
7003 if (NS_FAILED(rv)) {
7004 NS_WARNING("Selection::SetBaseAndExtentInLimiter() failed");
7005 return Err(rv);
7009 rv = AlignContentsAtRanges(selectionRanges, aAlignType, aEditingHost);
7010 if (NS_FAILED(rv)) {
7011 NS_WARNING("HTMLEditor::AlignContentsAtSelection() failed");
7012 return Err(rv);
7014 rv = selectionRanges.ApplyTo(SelectionRef());
7015 if (NS_FAILED(rv)) {
7016 NS_WARNING("AutoRangeArray::ApplyTo() failed");
7017 return Err(rv);
7020 if (MOZ_UNLIKELY(IsSelectionRangeContainerNotContent())) {
7021 NS_WARNING("Mutation event listener might have changed the selection");
7022 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
7025 rv = MaybeInsertPaddingBRElementForEmptyLastLineAtSelection();
7026 if (NS_FAILED(rv)) {
7027 NS_WARNING(
7028 "HTMLEditor::MaybeInsertPaddingBRElementForEmptyLastLineAtSelection() "
7029 "failed");
7030 return Err(rv);
7032 return EditActionResult::HandledResult();
7035 nsresult HTMLEditor::AlignContentsAtRanges(AutoRangeArray& aRanges,
7036 const nsAString& aAlignType,
7037 const Element& aEditingHost) {
7038 MOZ_ASSERT(IsEditActionDataAvailable());
7039 MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable());
7040 MOZ_ASSERT(aRanges.IsInContent());
7042 if (NS_WARN_IF(!aRanges.SaveAndTrackRanges(*this))) {
7043 return NS_ERROR_FAILURE;
7046 EditorDOMPoint pointToPutCaret;
7048 // Convert the selection ranges into "promoted" selection ranges: This
7049 // basically just expands the range to include the immediate block parent,
7050 // and then further expands to include any ancestors whose children are all
7051 // in the range
7052 AutoTArray<OwningNonNull<nsIContent>, 64> arrayOfContents;
7054 AutoRangeArray extendedRanges(aRanges);
7055 extendedRanges.ExtendRangesToWrapLines(
7056 EditSubAction::eSetOrClearAlignment,
7057 BlockInlineCheck::UseHTMLDefaultStyle, aEditingHost);
7058 Result<EditorDOMPoint, nsresult> splitResult =
7059 extendedRanges
7060 .SplitTextAtEndBoundariesAndInlineAncestorsAtBothBoundaries(
7061 *this, BlockInlineCheck::UseHTMLDefaultStyle, aEditingHost);
7062 if (MOZ_UNLIKELY(splitResult.isErr())) {
7063 NS_WARNING(
7064 "AutoRangeArray::"
7065 "SplitTextAtEndBoundariesAndInlineAncestorsAtBothBoundaries() "
7066 "failed");
7067 return splitResult.unwrapErr();
7069 if (splitResult.inspect().IsSet()) {
7070 pointToPutCaret = splitResult.unwrap();
7072 nsresult rv = extendedRanges.CollectEditTargetNodes(
7073 *this, arrayOfContents, EditSubAction::eSetOrClearAlignment,
7074 AutoRangeArray::CollectNonEditableNodes::Yes);
7075 if (NS_FAILED(rv)) {
7076 NS_WARNING(
7077 "AutoRangeArray::CollectEditTargetNodes(EditSubAction::"
7078 "eSetOrClearAlignment, CollectNonEditableNodes::Yes) failed");
7079 return rv;
7083 Result<EditorDOMPoint, nsresult> splitAtBRElementsResult =
7084 MaybeSplitElementsAtEveryBRElement(arrayOfContents,
7085 EditSubAction::eSetOrClearAlignment);
7086 if (MOZ_UNLIKELY(splitAtBRElementsResult.isErr())) {
7087 NS_WARNING(
7088 "HTMLEditor::MaybeSplitElementsAtEveryBRElement(EditSubAction::"
7089 "eSetOrClearAlignment) failed");
7090 return splitAtBRElementsResult.inspectErr();
7092 if (splitAtBRElementsResult.inspect().IsSet()) {
7093 pointToPutCaret = splitAtBRElementsResult.unwrap();
7096 // If we don't have any nodes, or we have only a single br, then we are
7097 // creating an empty alignment div. We have to do some different things for
7098 // these.
7099 bool createEmptyDivElement = arrayOfContents.IsEmpty();
7100 if (arrayOfContents.Length() == 1) {
7101 OwningNonNull<nsIContent>& content = arrayOfContents[0];
7103 if (HTMLEditUtils::SupportsAlignAttr(content) &&
7104 HTMLEditUtils::IsBlockElement(content,
7105 BlockInlineCheck::UseHTMLDefaultStyle)) {
7106 // The node is a table element, an hr, a paragraph, a div or a section
7107 // header; in HTML 4, it can directly carry the ALIGN attribute and we
7108 // don't need to make a div! If we are in CSS mode, all the work is done
7109 // in SetBlockElementAlign().
7110 Result<EditorDOMPoint, nsresult> pointToPutCaretOrError =
7111 SetBlockElementAlign(MOZ_KnownLive(*content->AsElement()), aAlignType,
7112 EditTarget::OnlyDescendantsExceptTable);
7113 if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) {
7114 NS_WARNING("HTMLEditor::SetBlockElementAlign() failed");
7115 return pointToPutCaretOrError.unwrapErr();
7117 if (pointToPutCaretOrError.inspect().IsSet()) {
7118 pointToPutCaret = pointToPutCaretOrError.unwrap();
7122 if (content->IsHTMLElement(nsGkAtoms::br)) {
7123 // The special case createEmptyDivElement code (below) that consumes
7124 // `<br>` elements can cause tables to split if the start node of the
7125 // selection is not in a table cell or caption, for example parent is a
7126 // `<tr>`. Avoid this unnecessary splitting if possible by leaving
7127 // createEmptyDivElement false so that we fall through to the normal case
7128 // alignment code.
7130 // XXX: It seems a little error prone for the createEmptyDivElement
7131 // special case code to assume that the start node of the selection
7132 // is the parent of the single node in the arrayOfContents, as the
7133 // paragraph above points out. Do we rely on the selection start
7134 // node because of the fact that arrayOfContents can be empty? We
7135 // should probably revisit this issue. - kin
7137 const EditorDOMPoint firstRangeStartPoint =
7138 pointToPutCaret.IsSet()
7139 ? pointToPutCaret
7140 : aRanges.GetFirstRangeStartPoint<EditorDOMPoint>();
7141 if (NS_WARN_IF(!firstRangeStartPoint.IsSet())) {
7142 return NS_ERROR_FAILURE;
7144 nsINode* parent = firstRangeStartPoint.GetContainer();
7145 createEmptyDivElement = !HTMLEditUtils::IsAnyTableElement(parent) ||
7146 HTMLEditUtils::IsTableCellOrCaption(*parent);
7150 if (createEmptyDivElement) {
7151 if (MOZ_UNLIKELY(!pointToPutCaret.IsSet() && !aRanges.IsInContent())) {
7152 NS_WARNING("Mutation event listener might have changed the selection");
7153 return NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE;
7155 const EditorDOMPoint pointToInsertDivElement =
7156 pointToPutCaret.IsSet()
7157 ? pointToPutCaret
7158 : aRanges.GetFirstRangeStartPoint<EditorDOMPoint>();
7159 Result<CreateElementResult, nsresult> insertNewDivElementResult =
7160 InsertDivElementToAlignContents(pointToInsertDivElement, aAlignType,
7161 aEditingHost);
7162 if (insertNewDivElementResult.isErr()) {
7163 NS_WARNING("HTMLEditor::InsertDivElementToAlignContents() failed");
7164 return insertNewDivElementResult.unwrapErr();
7166 CreateElementResult unwrappedInsertNewDivElementResult =
7167 insertNewDivElementResult.unwrap();
7168 aRanges.ClearSavedRanges();
7169 EditorDOMPoint pointToPutCaret =
7170 unwrappedInsertNewDivElementResult.UnwrapCaretPoint();
7171 nsresult rv = aRanges.Collapse(pointToPutCaret);
7172 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "AutoRangeArray::Collapse() failed");
7173 return rv;
7176 Result<CreateElementResult, nsresult> maybeCreateDivElementResult =
7177 AlignNodesAndDescendants(arrayOfContents, aAlignType, aEditingHost);
7178 if (MOZ_UNLIKELY(maybeCreateDivElementResult.isErr())) {
7179 NS_WARNING("HTMLEditor::AlignNodesAndDescendants() failed");
7180 return maybeCreateDivElementResult.unwrapErr();
7182 maybeCreateDivElementResult.inspect().IgnoreCaretPointSuggestion();
7184 MOZ_ASSERT(aRanges.HasSavedRanges());
7185 aRanges.RestoreFromSavedRanges();
7186 // If restored range is collapsed outside the latest cased <div> element,
7187 // we should move caret into the <div>.
7188 if (maybeCreateDivElementResult.inspect().GetNewNode() &&
7189 aRanges.IsCollapsed() && !aRanges.Ranges().IsEmpty()) {
7190 const auto firstRangeStartRawPoint =
7191 aRanges.GetFirstRangeStartPoint<EditorRawDOMPoint>();
7192 if (MOZ_LIKELY(firstRangeStartRawPoint.IsSet())) {
7193 Result<EditorRawDOMPoint, nsresult> pointInNewDivOrError =
7194 HTMLEditUtils::ComputePointToPutCaretInElementIfOutside<
7195 EditorRawDOMPoint>(
7196 *maybeCreateDivElementResult.inspect().GetNewNode(),
7197 firstRangeStartRawPoint);
7198 if (MOZ_UNLIKELY(pointInNewDivOrError.isErr())) {
7199 NS_WARNING(
7200 "HTMLEditUtils::ComputePointToPutCaretInElementIfOutside() failed, "
7201 "but ignored");
7202 } else if (pointInNewDivOrError.inspect().IsSet()) {
7203 nsresult rv = aRanges.Collapse(pointInNewDivOrError.unwrap());
7204 if (NS_FAILED(rv)) {
7205 NS_WARNING("AutoRangeArray::Collapse() failed");
7206 return rv;
7211 return NS_OK;
7214 Result<CreateElementResult, nsresult>
7215 HTMLEditor::InsertDivElementToAlignContents(
7216 const EditorDOMPoint& aPointToInsert, const nsAString& aAlignType,
7217 const Element& aEditingHost) {
7218 MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable());
7219 MOZ_ASSERT(!IsSelectionRangeContainerNotContent());
7220 MOZ_ASSERT(aPointToInsert.IsSetAndValid());
7222 if (NS_WARN_IF(!aPointToInsert.IsSet())) {
7223 return Err(NS_ERROR_FAILURE);
7226 Result<CreateElementResult, nsresult> createNewDivElementResult =
7227 InsertElementWithSplittingAncestorsWithTransaction(
7228 *nsGkAtoms::div, aPointToInsert, BRElementNextToSplitPoint::Delete,
7229 aEditingHost);
7230 if (MOZ_UNLIKELY(createNewDivElementResult.isErr())) {
7231 NS_WARNING(
7232 "HTMLEditor::InsertElementWithSplittingAncestorsWithTransaction("
7233 "nsGkAtoms::div, BRElementNextToSplitPoint::Delete) failed");
7234 return createNewDivElementResult;
7236 CreateElementResult unwrappedCreateNewDivElementResult =
7237 createNewDivElementResult.unwrap();
7238 // We'll suggest start of the new <div>, so we don't need the suggested
7239 // position.
7240 unwrappedCreateNewDivElementResult.IgnoreCaretPointSuggestion();
7242 MOZ_ASSERT(unwrappedCreateNewDivElementResult.GetNewNode());
7243 RefPtr<Element> newDivElement =
7244 unwrappedCreateNewDivElementResult.UnwrapNewNode();
7245 // Set up the alignment on the div, using HTML or CSS
7246 Result<EditorDOMPoint, nsresult> pointToPutCaretOrError =
7247 SetBlockElementAlign(*newDivElement, aAlignType,
7248 EditTarget::OnlyDescendantsExceptTable);
7249 if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) {
7250 NS_WARNING(
7251 "HTMLEditor::SetBlockElementAlign(EditTarget::"
7252 "OnlyDescendantsExceptTable) failed");
7253 return pointToPutCaretOrError.propagateErr();
7255 // We don't need the new suggested position too.
7257 // Put in a padding <br> element for empty last line so that it won't get
7258 // deleted.
7260 Result<CreateElementResult, nsresult> insertPaddingBRElementResult =
7261 InsertPaddingBRElementForEmptyLastLineWithTransaction(
7262 EditorDOMPoint(newDivElement, 0u));
7263 if (MOZ_UNLIKELY(insertPaddingBRElementResult.isErr())) {
7264 NS_WARNING(
7265 "HTMLEditor::InsertPaddingBRElementForEmptyLastLineWithTransaction() "
7266 "failed");
7267 return insertPaddingBRElementResult;
7269 insertPaddingBRElementResult.inspect().IgnoreCaretPointSuggestion();
7272 return CreateElementResult(std::move(newDivElement),
7273 EditorDOMPoint(newDivElement, 0u));
7276 Result<CreateElementResult, nsresult> HTMLEditor::AlignNodesAndDescendants(
7277 nsTArray<OwningNonNull<nsIContent>>& aArrayOfContents,
7278 const nsAString& aAlignType, const Element& aEditingHost) {
7279 // Detect all the transitions in the array, where a transition means that
7280 // adjacent nodes in the array don't have the same parent.
7281 AutoTArray<bool, 64> transitionList;
7282 HTMLEditor::MakeTransitionList(aArrayOfContents, transitionList);
7284 RefPtr<Element> latestCreatedDivElement;
7285 EditorDOMPoint pointToPutCaret;
7287 // Okay, now go through all the nodes and give them an align attrib or put
7288 // them in a div, or whatever is appropriate. Woohoo!
7290 RefPtr<Element> createdDivElement;
7291 const bool useCSS = IsCSSEnabled();
7292 int32_t indexOfTransitionList = -1;
7293 for (OwningNonNull<nsIContent>& content : aArrayOfContents) {
7294 ++indexOfTransitionList;
7296 // Ignore all non-editable nodes. Leave them be.
7297 if (!EditorUtils::IsEditableContent(content, EditorType::HTML)) {
7298 continue;
7301 // The node is a table element, an hr, a paragraph, a div or a section
7302 // header; in HTML 4, it can directly carry the ALIGN attribute and we
7303 // don't need to nest it, just set the alignment. In CSS, assign the
7304 // corresponding CSS styles in SetBlockElementAlign().
7305 if (HTMLEditUtils::SupportsAlignAttr(content)) {
7306 Result<EditorDOMPoint, nsresult> pointToPutCaretOrError =
7307 SetBlockElementAlign(MOZ_KnownLive(*content->AsElement()), aAlignType,
7308 EditTarget::NodeAndDescendantsExceptTable);
7309 if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) {
7310 NS_WARNING(
7311 "HTMLEditor::SetBlockElementAlign(EditTarget::"
7312 "NodeAndDescendantsExceptTable) failed");
7313 return pointToPutCaretOrError.propagateErr();
7315 if (pointToPutCaretOrError.inspect().IsSet()) {
7316 pointToPutCaret = pointToPutCaretOrError.unwrap();
7318 // Clear out createdDivElement so that we don't put nodes after this one
7319 // into it
7320 createdDivElement = nullptr;
7321 continue;
7324 EditorDOMPoint atContent(content);
7325 if (NS_WARN_IF(!atContent.IsSet())) {
7326 continue;
7329 // Skip insignificant formatting text nodes to prevent unnecessary
7330 // structure splitting!
7331 if (content->IsText() &&
7332 ((HTMLEditUtils::IsAnyTableElement(atContent.GetContainer()) &&
7333 !HTMLEditUtils::IsTableCellOrCaption(*atContent.GetContainer())) ||
7334 HTMLEditUtils::IsAnyListElement(atContent.GetContainer()) ||
7335 HTMLEditUtils::IsEmptyNode(
7336 *content,
7337 {EmptyCheckOption::TreatSingleBRElementAsVisible,
7338 EmptyCheckOption::TreatNonEditableContentAsInvisible}))) {
7339 continue;
7342 // If it's a list item, or a list inside a list, forget any "current" div,
7343 // and instead put divs inside the appropriate block (td, li, etc.)
7344 if (HTMLEditUtils::IsListItem(content) ||
7345 HTMLEditUtils::IsAnyListElement(content)) {
7346 Element* listOrListItemElement = content->AsElement();
7348 AutoEditorDOMPointOffsetInvalidator lockChild(atContent);
7349 // MOZ_KnownLive(*listOrListItemElement): An element of aArrayOfContents
7350 // which is array of OwningNonNull.
7351 Result<EditorDOMPoint, nsresult> pointToPutCaretOrError =
7352 RemoveAlignFromDescendants(MOZ_KnownLive(*listOrListItemElement),
7353 aAlignType,
7354 EditTarget::OnlyDescendantsExceptTable);
7355 if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) {
7356 NS_WARNING(
7357 "HTMLEditor::RemoveAlignFromDescendants(EditTarget::"
7358 "OnlyDescendantsExceptTable) failed");
7359 return pointToPutCaretOrError.propagateErr();
7361 if (pointToPutCaretOrError.inspect().IsSet()) {
7362 pointToPutCaret = pointToPutCaretOrError.unwrap();
7365 if (NS_WARN_IF(!atContent.IsSetAndValid())) {
7366 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
7369 if (useCSS) {
7370 nsStyledElement* styledListOrListItemElement =
7371 nsStyledElement::FromNode(listOrListItemElement);
7372 if (styledListOrListItemElement &&
7373 EditorElementStyle::Align().IsCSSSettable(
7374 *styledListOrListItemElement)) {
7375 // MOZ_KnownLive(*styledListOrListItemElement): An element of
7376 // aArrayOfContents which is array of OwningNonNull.
7377 Result<size_t, nsresult> result =
7378 CSSEditUtils::SetCSSEquivalentToStyle(
7379 WithTransaction::Yes, *this,
7380 MOZ_KnownLive(*styledListOrListItemElement),
7381 EditorElementStyle::Align(), &aAlignType);
7382 if (MOZ_UNLIKELY(result.isErr())) {
7383 if (NS_WARN_IF(result.inspectErr() == NS_ERROR_EDITOR_DESTROYED)) {
7384 return result.propagateErr();
7386 NS_WARNING(
7387 "CSSEditUtils::SetCSSEquivalentToStyle(EditorElementStyle::"
7388 "Align()) failed, but ignored");
7391 createdDivElement = nullptr;
7392 continue;
7395 if (HTMLEditUtils::IsAnyListElement(atContent.GetContainer())) {
7396 // If we don't use CSS, add a content to list element: they have to
7397 // be inside another list, i.e., >= second level of nesting.
7398 // XXX AlignContentsInAllTableCellsAndListItems() handles only list
7399 // item elements and table cells. Is it intentional? Why don't
7400 // we need to align contents in other type blocks?
7401 // MOZ_KnownLive(*listOrListItemElement): An element of aArrayOfContents
7402 // which is array of OwningNonNull.
7403 Result<EditorDOMPoint, nsresult> pointToPutCaretOrError =
7404 AlignContentsInAllTableCellsAndListItems(
7405 MOZ_KnownLive(*listOrListItemElement), aAlignType);
7406 if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) {
7407 NS_WARNING(
7408 "HTMLEditor::AlignContentsInAllTableCellsAndListItems() failed");
7409 return pointToPutCaretOrError.propagateErr();
7411 if (pointToPutCaretOrError.inspect().IsSet()) {
7412 pointToPutCaret = pointToPutCaretOrError.unwrap();
7414 createdDivElement = nullptr;
7415 continue;
7418 // Clear out createdDivElement so that we don't put nodes after this one
7419 // into it
7422 // Need to make a div to put things in if we haven't already, or if this
7423 // node doesn't go in div we used earlier.
7424 if (!createdDivElement || transitionList[indexOfTransitionList]) {
7425 // First, check that our element can contain a div.
7426 if (!HTMLEditUtils::CanNodeContain(*atContent.GetContainer(),
7427 *nsGkAtoms::div)) {
7428 // XXX Why do we return "OK" here rather than returning error or
7429 // doing continue?
7430 return latestCreatedDivElement
7431 ? CreateElementResult(std::move(latestCreatedDivElement),
7432 std::move(pointToPutCaret))
7433 : CreateElementResult::NotHandled(
7434 std::move(pointToPutCaret));
7437 Result<CreateElementResult, nsresult> createNewDivElementResult =
7438 InsertElementWithSplittingAncestorsWithTransaction(
7439 *nsGkAtoms::div, atContent, BRElementNextToSplitPoint::Keep,
7440 aEditingHost);
7441 if (MOZ_UNLIKELY(createNewDivElementResult.isErr())) {
7442 NS_WARNING(
7443 "HTMLEditor::InsertElementWithSplittingAncestorsWithTransaction("
7444 "nsGkAtoms::div) failed");
7445 return createNewDivElementResult;
7447 CreateElementResult unwrappedCreateNewDivElementResult =
7448 createNewDivElementResult.unwrap();
7449 if (unwrappedCreateNewDivElementResult.HasCaretPointSuggestion()) {
7450 pointToPutCaret = unwrappedCreateNewDivElementResult.UnwrapCaretPoint();
7453 MOZ_ASSERT(unwrappedCreateNewDivElementResult.GetNewNode());
7454 createdDivElement = unwrappedCreateNewDivElementResult.UnwrapNewNode();
7455 // Set up the alignment on the div
7456 Result<EditorDOMPoint, nsresult> pointToPutCaretOrError =
7457 SetBlockElementAlign(*createdDivElement, aAlignType,
7458 EditTarget::OnlyDescendantsExceptTable);
7459 if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) {
7460 if (NS_WARN_IF(pointToPutCaretOrError.inspectErr() ==
7461 NS_ERROR_EDITOR_DESTROYED)) {
7462 return pointToPutCaretOrError.propagateErr();
7464 NS_WARNING(
7465 "HTMLEditor::SetBlockElementAlign(EditTarget::"
7466 "OnlyDescendantsExceptTable) failed, but ignored");
7467 } else if (pointToPutCaretOrError.inspect().IsSet()) {
7468 pointToPutCaret = pointToPutCaretOrError.unwrap();
7470 latestCreatedDivElement = createdDivElement;
7473 // Tuck the node into the end of the active div
7475 // MOZ_KnownLive because 'aArrayOfContents' is guaranteed to keep it alive.
7476 Result<MoveNodeResult, nsresult> moveNodeResult =
7477 MoveNodeToEndWithTransaction(MOZ_KnownLive(content),
7478 *createdDivElement);
7479 if (MOZ_UNLIKELY(moveNodeResult.isErr())) {
7480 NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed");
7481 return moveNodeResult.propagateErr();
7483 MoveNodeResult unwrappedMoveNodeResult = moveNodeResult.unwrap();
7484 if (unwrappedMoveNodeResult.HasCaretPointSuggestion()) {
7485 pointToPutCaret = unwrappedMoveNodeResult.UnwrapCaretPoint();
7489 return latestCreatedDivElement
7490 ? CreateElementResult(std::move(latestCreatedDivElement),
7491 std::move(pointToPutCaret))
7492 : CreateElementResult::NotHandled(std::move(pointToPutCaret));
7495 Result<EditorDOMPoint, nsresult>
7496 HTMLEditor::AlignContentsInAllTableCellsAndListItems(
7497 Element& aElement, const nsAString& aAlignType) {
7498 MOZ_ASSERT(IsEditActionDataAvailable());
7500 // Gather list of table cells or list items
7501 AutoTArray<OwningNonNull<Element>, 64> arrayOfTableCellsAndListItems;
7502 DOMIterator iter(aElement);
7503 iter.AppendNodesToArray(
7504 +[](nsINode& aNode, void*) -> bool {
7505 MOZ_ASSERT(Element::FromNode(&aNode));
7506 return HTMLEditUtils::IsTableCell(&aNode) ||
7507 HTMLEditUtils::IsListItem(&aNode);
7509 arrayOfTableCellsAndListItems);
7511 // Now that we have the list, align their contents as requested
7512 EditorDOMPoint pointToPutCaret;
7513 for (auto& tableCellOrListItemElement : arrayOfTableCellsAndListItems) {
7514 // MOZ_KnownLive because 'arrayOfTableCellsAndListItems' is guaranteed to
7515 // keep it alive.
7516 Result<EditorDOMPoint, nsresult> pointToPutCaretOrError =
7517 AlignBlockContentsWithDivElement(
7518 MOZ_KnownLive(tableCellOrListItemElement), aAlignType);
7519 if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) {
7520 NS_WARNING("HTMLEditor::AlignBlockContentsWithDivElement() failed");
7521 return pointToPutCaretOrError;
7523 if (pointToPutCaretOrError.inspect().IsSet()) {
7524 pointToPutCaret = pointToPutCaretOrError.unwrap();
7528 return pointToPutCaret;
7531 Result<EditorDOMPoint, nsresult> HTMLEditor::AlignBlockContentsWithDivElement(
7532 Element& aBlockElement, const nsAString& aAlignType) {
7533 MOZ_ASSERT(IsEditActionDataAvailable());
7535 // XXX I don't understand why we should NOT align non-editable children
7536 // with modifying EDITABLE `<div>` element.
7537 nsCOMPtr<nsIContent> firstEditableContent = HTMLEditUtils::GetFirstChild(
7538 aBlockElement, {WalkTreeOption::IgnoreNonEditableNode});
7539 if (!firstEditableContent) {
7540 // This block has no editable content, nothing to align.
7541 return EditorDOMPoint();
7544 // If there is only one editable content and it's a `<div>` element,
7545 // just set `align` attribute of it.
7546 nsCOMPtr<nsIContent> lastEditableContent = HTMLEditUtils::GetLastChild(
7547 aBlockElement, {WalkTreeOption::IgnoreNonEditableNode});
7548 if (firstEditableContent == lastEditableContent &&
7549 firstEditableContent->IsHTMLElement(nsGkAtoms::div)) {
7550 nsresult rv = SetAttributeOrEquivalent(
7551 MOZ_KnownLive(firstEditableContent->AsElement()), nsGkAtoms::align,
7552 aAlignType, false);
7553 if (NS_WARN_IF(Destroyed())) {
7554 NS_WARNING(
7555 "EditorBase::SetAttributeOrEquivalent(nsGkAtoms::align) caused "
7556 "destroying the editor");
7557 return Err(NS_ERROR_EDITOR_DESTROYED);
7559 if (NS_FAILED(rv)) {
7560 NS_WARNING(
7561 "EditorBase::SetAttributeOrEquivalent(nsGkAtoms::align) failed");
7562 return Err(rv);
7564 return EditorDOMPoint();
7567 // Otherwise, we need to insert a `<div>` element to set `align` attribute.
7568 // XXX Don't insert the new `<div>` element until we set `align` attribute
7569 // for avoiding running mutation event listeners.
7570 Result<CreateElementResult, nsresult> createNewDivElementResult =
7571 CreateAndInsertElement(
7572 WithTransaction::Yes, *nsGkAtoms::div,
7573 EditorDOMPoint(&aBlockElement, 0u),
7574 // MOZ_CAN_RUN_SCRIPT_BOUNDARY due to bug 1758868
7575 [&aAlignType](HTMLEditor& aHTMLEditor, Element& aDivElement,
7576 const EditorDOMPoint&) MOZ_CAN_RUN_SCRIPT_BOUNDARY {
7577 MOZ_ASSERT(!aDivElement.IsInComposedDoc());
7578 // If aDivElement has not been connected yet, we do not need
7579 // transaction of setting align attribute here.
7580 nsresult rv = aHTMLEditor.SetAttributeOrEquivalent(
7581 &aDivElement, nsGkAtoms::align, aAlignType, false);
7582 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
7583 "EditorBase::SetAttributeOrEquivalent("
7584 "nsGkAtoms::align, \"...\", false) failed");
7585 return rv;
7587 if (MOZ_UNLIKELY(createNewDivElementResult.isErr())) {
7588 NS_WARNING(
7589 "HTMLEditor::CreateAndInsertElement(WithTransaction::Yes, "
7590 "nsGkAtoms::div) failed");
7591 return createNewDivElementResult.propagateErr();
7593 CreateElementResult unwrappedCreateNewDivElementResult =
7594 createNewDivElementResult.unwrap();
7595 EditorDOMPoint pointToPutCaret =
7596 unwrappedCreateNewDivElementResult.UnwrapCaretPoint();
7597 RefPtr<Element> newDivElement =
7598 unwrappedCreateNewDivElementResult.UnwrapNewNode();
7599 MOZ_ASSERT(newDivElement);
7600 // XXX This is tricky and does not work with mutation event listeners.
7601 // But I'm not sure what we should do if new content is inserted.
7602 // Anyway, I don't think that we should move editable contents
7603 // over non-editable contents. Chrome does no do that.
7604 while (lastEditableContent && (lastEditableContent != newDivElement)) {
7605 Result<MoveNodeResult, nsresult> moveNodeResult = MoveNodeWithTransaction(
7606 *lastEditableContent, EditorDOMPoint(newDivElement, 0u));
7607 if (MOZ_UNLIKELY(moveNodeResult.isErr())) {
7608 NS_WARNING("HTMLEditor::MoveNodeWithTransaction() failed");
7609 return moveNodeResult.propagateErr();
7611 MoveNodeResult unwrappedMoveNodeResult = moveNodeResult.unwrap();
7612 if (unwrappedMoveNodeResult.HasCaretPointSuggestion()) {
7613 pointToPutCaret = unwrappedMoveNodeResult.UnwrapCaretPoint();
7615 lastEditableContent = HTMLEditUtils::GetLastChild(
7616 aBlockElement, {WalkTreeOption::IgnoreNonEditableNode});
7618 return pointToPutCaret;
7621 Result<EditorRawDOMRange, nsresult>
7622 HTMLEditor::GetRangeExtendedToHardLineEdgesForBlockEditAction(
7623 const nsRange* aRange, const Element& aEditingHost) const {
7624 MOZ_ASSERT(IsEditActionDataAvailable());
7626 // This tweaks selections to be more "natural".
7627 // Idea here is to adjust edges of selection ranges so that they do not cross
7628 // breaks or block boundaries unless something editable beyond that boundary
7629 // is also selected. This adjustment makes it much easier for the various
7630 // block operations to determine what nodes to act on.
7631 if (NS_WARN_IF(!aRange) || NS_WARN_IF(!aRange->IsPositioned())) {
7632 return Err(NS_ERROR_FAILURE);
7635 const EditorRawDOMPoint startPoint(aRange->StartRef());
7636 if (NS_WARN_IF(!startPoint.IsSet())) {
7637 return Err(NS_ERROR_FAILURE);
7639 const EditorRawDOMPoint endPoint(aRange->EndRef());
7640 if (NS_WARN_IF(!endPoint.IsSet())) {
7641 return Err(NS_ERROR_FAILURE);
7644 // adjusted values default to original values
7645 EditorRawDOMRange newRange(startPoint, endPoint);
7647 // Is there any intervening visible white-space? If so we can't push
7648 // selection past that, it would visibly change meaning of users selection.
7649 WSRunScanner wsScannerAtEnd(
7650 &aEditingHost, endPoint,
7651 // We should refer only the default style of HTML because we need to wrap
7652 // any elements with a specific HTML element. So we should not refer
7653 // actual style. For example, we want to reformat parent HTML block
7654 // element even if selected in a blocked phrase element or
7655 // non-HTMLelement.
7656 BlockInlineCheck::UseHTMLDefaultStyle);
7657 WSScanResult scanResultAtEnd =
7658 wsScannerAtEnd.ScanPreviousVisibleNodeOrBlockBoundaryFrom(endPoint);
7659 if (scanResultAtEnd.Failed()) {
7660 NS_WARNING(
7661 "WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundaryFrom() failed");
7662 return Err(NS_ERROR_FAILURE);
7664 if (scanResultAtEnd.ReachedSomethingNonTextContent()) {
7665 // eThisBlock and eOtherBlock conveniently distinguish cases
7666 // of going "down" into a block and "up" out of a block.
7667 if (wsScannerAtEnd.StartsFromOtherBlockElement()) {
7668 // endpoint is just after the close of a block.
7669 if (nsIContent* child = HTMLEditUtils::GetLastLeafContent(
7670 *wsScannerAtEnd.StartReasonOtherBlockElementPtr(),
7671 {LeafNodeType::LeafNodeOrChildBlock},
7672 BlockInlineCheck::UseHTMLDefaultStyle)) {
7673 newRange.SetEnd(EditorRawDOMPoint::After(*child));
7675 // else block is empty - we can leave selection alone here, i think.
7676 } else if (wsScannerAtEnd.StartsFromCurrentBlockBoundary()) {
7677 // endpoint is just after start of this block
7678 if (nsIContent* child = HTMLEditUtils::GetPreviousContent(
7679 endPoint, {WalkTreeOption::IgnoreNonEditableNode},
7680 BlockInlineCheck::UseHTMLDefaultStyle, &aEditingHost)) {
7681 newRange.SetEnd(EditorRawDOMPoint::After(*child));
7683 // else block is empty - we can leave selection alone here, i think.
7684 } else if (wsScannerAtEnd.StartsFromBRElement()) {
7685 // endpoint is just after break. lets adjust it to before it.
7686 newRange.SetEnd(
7687 EditorRawDOMPoint(wsScannerAtEnd.StartReasonBRElementPtr()));
7691 // Is there any intervening visible white-space? If so we can't push
7692 // selection past that, it would visibly change meaning of users selection.
7693 WSRunScanner wsScannerAtStart(&aEditingHost, startPoint,
7694 BlockInlineCheck::UseHTMLDefaultStyle);
7695 WSScanResult scanResultAtStart =
7696 wsScannerAtStart.ScanNextVisibleNodeOrBlockBoundaryFrom(startPoint);
7697 if (scanResultAtStart.Failed()) {
7698 NS_WARNING("WSRunScanner::ScanNextVisibleNodeOrBlockBoundaryFrom() failed");
7699 return Err(NS_ERROR_FAILURE);
7701 if (scanResultAtStart.ReachedSomethingNonTextContent()) {
7702 // eThisBlock and eOtherBlock conveniently distinguish cases
7703 // of going "down" into a block and "up" out of a block.
7704 if (wsScannerAtStart.EndsByOtherBlockElement()) {
7705 // startpoint is just before the start of a block.
7706 if (nsIContent* child = HTMLEditUtils::GetFirstLeafContent(
7707 *wsScannerAtStart.EndReasonOtherBlockElementPtr(),
7708 {LeafNodeType::LeafNodeOrChildBlock},
7709 BlockInlineCheck::UseHTMLDefaultStyle)) {
7710 newRange.SetStart(EditorRawDOMPoint(child));
7712 // else block is empty - we can leave selection alone here, i think.
7713 } else if (wsScannerAtStart.EndsByCurrentBlockBoundary()) {
7714 // startpoint is just before end of this block
7715 if (nsIContent* child = HTMLEditUtils::GetNextContent(
7716 startPoint, {WalkTreeOption::IgnoreNonEditableNode},
7717 BlockInlineCheck::UseHTMLDefaultStyle, &aEditingHost)) {
7718 newRange.SetStart(EditorRawDOMPoint(child));
7720 // else block is empty - we can leave selection alone here, i think.
7721 } else if (wsScannerAtStart.EndsByBRElement()) {
7722 // startpoint is just before a break. lets adjust it to after it.
7723 newRange.SetStart(
7724 EditorRawDOMPoint::After(*wsScannerAtStart.EndReasonBRElementPtr()));
7728 // There is a demented possibility we have to check for. We might have a very
7729 // strange selection that is not collapsed and yet does not contain any
7730 // editable content, and satisfies some of the above conditions that cause
7731 // tweaking. In this case we don't want to tweak the selection into a block
7732 // it was never in, etc. There are a variety of strategies one might use to
7733 // try to detect these cases, but I think the most straightforward is to see
7734 // if the adjusted locations "cross" the old values: i.e., new end before old
7735 // start, or new start after old end. If so then just leave things alone.
7737 Maybe<int32_t> comp = nsContentUtils::ComparePoints(
7738 startPoint.ToRawRangeBoundary(), newRange.EndRef().ToRawRangeBoundary());
7740 if (NS_WARN_IF(!comp)) {
7741 return Err(NS_ERROR_FAILURE);
7744 if (*comp == 1) {
7745 return EditorRawDOMRange(); // New end before old start.
7748 comp = nsContentUtils::ComparePoints(newRange.StartRef().ToRawRangeBoundary(),
7749 endPoint.ToRawRangeBoundary());
7751 if (NS_WARN_IF(!comp)) {
7752 return Err(NS_ERROR_FAILURE);
7755 if (*comp == 1) {
7756 return EditorRawDOMRange(); // New start after old end.
7759 return newRange;
7762 template <typename EditorDOMRangeType>
7763 already_AddRefed<nsRange> HTMLEditor::CreateRangeIncludingAdjuscentWhiteSpaces(
7764 const EditorDOMRangeType& aRange) {
7765 MOZ_DIAGNOSTIC_ASSERT(aRange.IsPositioned());
7766 return CreateRangeIncludingAdjuscentWhiteSpaces(aRange.StartRef(),
7767 aRange.EndRef());
7770 template <typename EditorDOMPointType1, typename EditorDOMPointType2>
7771 already_AddRefed<nsRange> HTMLEditor::CreateRangeIncludingAdjuscentWhiteSpaces(
7772 const EditorDOMPointType1& aStartPoint,
7773 const EditorDOMPointType2& aEndPoint) {
7774 MOZ_DIAGNOSTIC_ASSERT(!aStartPoint.IsInNativeAnonymousSubtree());
7775 MOZ_DIAGNOSTIC_ASSERT(!aEndPoint.IsInNativeAnonymousSubtree());
7777 if (!aStartPoint.IsInContentNode() || !aEndPoint.IsInContentNode()) {
7778 NS_WARNING_ASSERTION(aStartPoint.IsSet(), "aStartPoint was not set");
7779 NS_WARNING_ASSERTION(aEndPoint.IsSet(), "aEndPoint was not set");
7780 return nullptr;
7783 const Element* const editingHost = ComputeEditingHost();
7784 if (NS_WARN_IF(!editingHost)) {
7785 return nullptr;
7788 EditorDOMPoint startPoint = aStartPoint.template To<EditorDOMPoint>();
7789 EditorDOMPoint endPoint = aEndPoint.template To<EditorDOMPoint>();
7790 AutoRangeArray::UpdatePointsToSelectAllChildrenIfCollapsedInEmptyBlockElement(
7791 startPoint, endPoint, *editingHost);
7793 if (NS_WARN_IF(!startPoint.IsInContentNode()) ||
7794 NS_WARN_IF(!endPoint.IsInContentNode())) {
7795 NS_WARNING(
7796 "AutoRangeArray::"
7797 "UpdatePointsToSelectAllChildrenIfCollapsedInEmptyBlockElement() "
7798 "failed");
7799 return nullptr;
7802 // For text actions, we want to look backwards (or forwards, as
7803 // appropriate) for additional white-space or nbsp's. We may have to act
7804 // on these later even though they are outside of the initial selection.
7805 // Even if they are in another node!
7806 // XXX Those scanners do not treat siblings of the text nodes. Perhaps,
7807 // we should use `WSRunScanner::GetFirstASCIIWhiteSpacePointCollapsedTo()`
7808 // and `WSRunScanner::GetEndOfCollapsibleASCIIWhiteSpaces()` instead.
7809 if (startPoint.IsInTextNode()) {
7810 while (!startPoint.IsStartOfContainer()) {
7811 if (!startPoint.IsPreviousCharASCIISpaceOrNBSP()) {
7812 break;
7814 MOZ_ALWAYS_TRUE(startPoint.RewindOffset());
7817 if (!startPoint.GetChildOrContainerIfDataNode() ||
7818 !startPoint.GetChildOrContainerIfDataNode()->IsInclusiveDescendantOf(
7819 editingHost)) {
7820 return nullptr;
7822 if (endPoint.IsInTextNode()) {
7823 while (!endPoint.IsEndOfContainer()) {
7824 if (!endPoint.IsCharASCIISpaceOrNBSP()) {
7825 break;
7827 MOZ_ALWAYS_TRUE(endPoint.AdvanceOffset());
7830 EditorDOMPoint lastRawPoint(endPoint);
7831 if (!lastRawPoint.IsStartOfContainer()) {
7832 lastRawPoint.RewindOffset();
7834 if (!lastRawPoint.GetChildOrContainerIfDataNode() ||
7835 !lastRawPoint.GetChildOrContainerIfDataNode()->IsInclusiveDescendantOf(
7836 editingHost)) {
7837 return nullptr;
7840 RefPtr<nsRange> range =
7841 nsRange::Create(startPoint.ToRawRangeBoundary(),
7842 endPoint.ToRawRangeBoundary(), IgnoreErrors());
7843 NS_WARNING_ASSERTION(range, "nsRange::Create() failed");
7844 return range.forget();
7847 Result<EditorDOMPoint, nsresult> HTMLEditor::MaybeSplitElementsAtEveryBRElement(
7848 nsTArray<OwningNonNull<nsIContent>>& aArrayOfContents,
7849 EditSubAction aEditSubAction) {
7850 // Post-process the list to break up inline containers that contain br's, but
7851 // only for operations that might care, like making lists or paragraphs
7852 switch (aEditSubAction) {
7853 case EditSubAction::eCreateOrRemoveBlock:
7854 case EditSubAction::eFormatBlockForHTMLCommand:
7855 case EditSubAction::eMergeBlockContents:
7856 case EditSubAction::eCreateOrChangeList:
7857 case EditSubAction::eSetOrClearAlignment:
7858 case EditSubAction::eSetPositionToAbsolute:
7859 case EditSubAction::eIndent:
7860 case EditSubAction::eOutdent: {
7861 EditorDOMPoint pointToPutCaret;
7862 for (size_t index : Reversed(IntegerRange(aArrayOfContents.Length()))) {
7863 OwningNonNull<nsIContent>& content = aArrayOfContents[index];
7864 if (HTMLEditUtils::IsInlineContent(
7865 content, BlockInlineCheck::UseHTMLDefaultStyle) &&
7866 HTMLEditUtils::IsContainerNode(content) && !content->IsText()) {
7867 AutoTArray<OwningNonNull<nsIContent>, 24> arrayOfInlineContents;
7868 // MOZ_KnownLive because 'aArrayOfContents' is guaranteed to keep it
7869 // alive.
7870 Result<EditorDOMPoint, nsresult> splitResult =
7871 SplitElementsAtEveryBRElement(MOZ_KnownLive(content),
7872 arrayOfInlineContents);
7873 if (splitResult.isErr()) {
7874 NS_WARNING("HTMLEditor::SplitElementsAtEveryBRElement() failed");
7875 return splitResult;
7877 if (splitResult.inspect().IsSet()) {
7878 pointToPutCaret = splitResult.unwrap();
7880 // Put these nodes in aArrayOfContents, replacing the current node
7881 aArrayOfContents.RemoveElementAt(index);
7882 aArrayOfContents.InsertElementsAt(index, arrayOfInlineContents);
7885 return pointToPutCaret;
7887 default:
7888 return EditorDOMPoint();
7892 Result<EditorDOMPoint, nsresult>
7893 HTMLEditor::SplitInlineAncestorsAtRangeBoundaries(
7894 RangeItem& aRangeItem, BlockInlineCheck aBlockInlineCheck,
7895 const Element& aEditingHost,
7896 const nsIContent* aAncestorLimiter /* = nullptr */) {
7897 MOZ_ASSERT(IsEditActionDataAvailable());
7899 EditorDOMPoint pointToPutCaret;
7900 if (!aRangeItem.Collapsed() && aRangeItem.mEndContainer &&
7901 aRangeItem.mEndContainer->IsContent()) {
7902 nsCOMPtr<nsIContent> mostAncestorInlineContentAtEnd =
7903 HTMLEditUtils::GetMostDistantAncestorInlineElement(
7904 *aRangeItem.mEndContainer->AsContent(), aBlockInlineCheck,
7905 &aEditingHost, aAncestorLimiter);
7907 if (mostAncestorInlineContentAtEnd) {
7908 Result<SplitNodeResult, nsresult> splitEndInlineResult =
7909 SplitNodeDeepWithTransaction(
7910 *mostAncestorInlineContentAtEnd, aRangeItem.EndPoint(),
7911 SplitAtEdges::eDoNotCreateEmptyContainer);
7912 if (MOZ_UNLIKELY(splitEndInlineResult.isErr())) {
7913 NS_WARNING(
7914 "HTMLEditor::SplitNodeDeepWithTransaction(SplitAtEdges::"
7915 "eDoNotCreateEmptyContainer) failed");
7916 return splitEndInlineResult.propagateErr();
7918 SplitNodeResult unwrappedSplitEndInlineResult =
7919 splitEndInlineResult.unwrap();
7920 unwrappedSplitEndInlineResult.MoveCaretPointTo(
7921 pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
7922 if (pointToPutCaret.IsInContentNode() &&
7923 MOZ_UNLIKELY(
7924 &aEditingHost !=
7925 ComputeEditingHost(*pointToPutCaret.ContainerAs<nsIContent>()))) {
7926 NS_WARNING(
7927 "HTMLEditor::SplitNodeDeepWithTransaction(SplitAtEdges::"
7928 "eDoNotCreateEmptyContainer) caused changing editing host");
7929 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
7931 const auto splitPointAtEnd =
7932 unwrappedSplitEndInlineResult.AtSplitPoint<EditorRawDOMPoint>();
7933 if (MOZ_UNLIKELY(!splitPointAtEnd.IsSet())) {
7934 NS_WARNING(
7935 "HTMLEditor::SplitNodeDeepWithTransaction(SplitAtEdges::"
7936 "eDoNotCreateEmptyContainer) didn't return split point");
7937 return Err(NS_ERROR_FAILURE);
7939 aRangeItem.mEndContainer = splitPointAtEnd.GetContainer();
7940 aRangeItem.mEndOffset = splitPointAtEnd.Offset();
7944 if (!aRangeItem.mStartContainer || !aRangeItem.mStartContainer->IsContent()) {
7945 return pointToPutCaret;
7948 nsCOMPtr<nsIContent> mostAncestorInlineContentAtStart =
7949 HTMLEditUtils::GetMostDistantAncestorInlineElement(
7950 *aRangeItem.mStartContainer->AsContent(), aBlockInlineCheck,
7951 &aEditingHost, aAncestorLimiter);
7953 if (mostAncestorInlineContentAtStart) {
7954 Result<SplitNodeResult, nsresult> splitStartInlineResult =
7955 SplitNodeDeepWithTransaction(*mostAncestorInlineContentAtStart,
7956 aRangeItem.StartPoint(),
7957 SplitAtEdges::eDoNotCreateEmptyContainer);
7958 if (MOZ_UNLIKELY(splitStartInlineResult.isErr())) {
7959 NS_WARNING(
7960 "HTMLEditor::SplitNodeDeepWithTransaction(SplitAtEdges::"
7961 "eDoNotCreateEmptyContainer) failed");
7962 return splitStartInlineResult.propagateErr();
7964 SplitNodeResult unwrappedSplitStartInlineResult =
7965 splitStartInlineResult.unwrap();
7966 // XXX Why don't we check editing host like above??
7967 unwrappedSplitStartInlineResult.MoveCaretPointTo(
7968 pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
7969 // XXX If we split only here because of collapsed range, we're modifying
7970 // only start point of aRangeItem. Shouldn't we modify end point here
7971 // if it's collapsed?
7972 const auto splitPointAtStart =
7973 unwrappedSplitStartInlineResult.AtSplitPoint<EditorRawDOMPoint>();
7974 if (MOZ_UNLIKELY(!splitPointAtStart.IsSet())) {
7975 NS_WARNING(
7976 "HTMLEditor::SplitNodeDeepWithTransaction(SplitAtEdges::"
7977 "eDoNotCreateEmptyContainer) didn't return split point");
7978 return Err(NS_ERROR_FAILURE);
7980 aRangeItem.mStartContainer = splitPointAtStart.GetContainer();
7981 aRangeItem.mStartOffset = splitPointAtStart.Offset();
7984 return pointToPutCaret;
7987 Result<EditorDOMPoint, nsresult> HTMLEditor::SplitElementsAtEveryBRElement(
7988 nsIContent& aMostAncestorToBeSplit,
7989 nsTArray<OwningNonNull<nsIContent>>& aOutArrayOfContents) {
7990 MOZ_ASSERT(IsEditActionDataAvailable());
7992 // First build up a list of all the break nodes inside the inline container.
7993 AutoTArray<OwningNonNull<HTMLBRElement>, 24> arrayOfBRElements;
7994 DOMIterator iter(aMostAncestorToBeSplit);
7995 iter.AppendAllNodesToArray(arrayOfBRElements);
7997 // If there aren't any breaks, just put inNode itself in the array
7998 if (arrayOfBRElements.IsEmpty()) {
7999 aOutArrayOfContents.AppendElement(aMostAncestorToBeSplit);
8000 return EditorDOMPoint();
8003 // Else we need to bust up aMostAncestorToBeSplit along all the breaks
8004 nsCOMPtr<nsIContent> nextContent = &aMostAncestorToBeSplit;
8005 EditorDOMPoint pointToPutCaret;
8006 for (OwningNonNull<HTMLBRElement>& brElement : arrayOfBRElements) {
8007 EditorDOMPoint atBRNode(brElement);
8008 if (NS_WARN_IF(!atBRNode.IsSet())) {
8009 return Err(NS_ERROR_FAILURE);
8011 Result<SplitNodeResult, nsresult> splitNodeResult =
8012 SplitNodeDeepWithTransaction(
8013 *nextContent, atBRNode, SplitAtEdges::eAllowToCreateEmptyContainer);
8014 if (MOZ_UNLIKELY(splitNodeResult.isErr())) {
8015 NS_WARNING("HTMLEditor::SplitNodeDeepWithTransaction() failed");
8016 return splitNodeResult.propagateErr();
8018 SplitNodeResult unwrappedSplitNodeResult = splitNodeResult.unwrap();
8019 unwrappedSplitNodeResult.MoveCaretPointTo(
8020 pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
8021 // Put previous node at the split point.
8022 if (nsIContent* previousContent =
8023 unwrappedSplitNodeResult.GetPreviousContent()) {
8024 // Might not be a left node. A break might have been at the very
8025 // beginning of inline container, in which case
8026 // SplitNodeDeepWithTransaction() would not actually split anything.
8027 aOutArrayOfContents.AppendElement(*previousContent);
8030 // Move break outside of container and also put in node list
8031 // MOZ_KnownLive because 'arrayOfBRElements' is guaranteed to keep it alive.
8032 Result<MoveNodeResult, nsresult> moveBRElementResult =
8033 MoveNodeWithTransaction(
8034 MOZ_KnownLive(brElement),
8035 unwrappedSplitNodeResult.AtNextContent<EditorDOMPoint>());
8036 if (MOZ_UNLIKELY(moveBRElementResult.isErr())) {
8037 NS_WARNING("HTMLEditor::MoveNodeWithTransaction() failed");
8038 return moveBRElementResult.propagateErr();
8040 MoveNodeResult unwrappedMoveBRElementResult = moveBRElementResult.unwrap();
8041 unwrappedMoveBRElementResult.MoveCaretPointTo(
8042 pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
8043 aOutArrayOfContents.AppendElement(brElement);
8045 nextContent = unwrappedSplitNodeResult.GetNextContent();
8048 // Now tack on remaining next node.
8049 aOutArrayOfContents.AppendElement(*nextContent);
8051 return pointToPutCaret;
8054 // static
8055 void HTMLEditor::MakeTransitionList(
8056 const nsTArray<OwningNonNull<nsIContent>>& aArrayOfContents,
8057 nsTArray<bool>& aTransitionArray) {
8058 nsINode* prevParent = nullptr;
8059 aTransitionArray.EnsureLengthAtLeast(aArrayOfContents.Length());
8060 for (uint32_t i = 0; i < aArrayOfContents.Length(); i++) {
8061 aTransitionArray[i] = aArrayOfContents[i]->GetParentNode() != prevParent;
8062 prevParent = aArrayOfContents[i]->GetParentNode();
8066 Result<InsertParagraphResult, nsresult>
8067 HTMLEditor::HandleInsertParagraphInHeadingElement(
8068 Element& aHeadingElement, const EditorDOMPoint& aPointToSplit) {
8069 MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable());
8071 auto splitHeadingResult =
8072 [this, &aPointToSplit, &aHeadingElement]()
8073 MOZ_CAN_RUN_SCRIPT -> Result<SplitNodeResult, nsresult> {
8074 // Normalize collapsible white-spaces around the split point to keep
8075 // them visible after the split. Note that this does not touch
8076 // selection because of using AutoTransactionsConserveSelection in
8077 // WhiteSpaceVisibilityKeeper::ReplaceTextAndRemoveEmptyTextNodes().
8078 Result<EditorDOMPoint, nsresult> preparationResult =
8079 WhiteSpaceVisibilityKeeper::PrepareToSplitBlockElement(
8080 *this, aPointToSplit, aHeadingElement);
8081 if (MOZ_UNLIKELY(preparationResult.isErr())) {
8082 NS_WARNING(
8083 "WhiteSpaceVisibilityKeeper::PrepareToSplitBlockElement() "
8084 "failed");
8085 return preparationResult.propagateErr();
8087 EditorDOMPoint pointToSplit = preparationResult.unwrap();
8088 MOZ_ASSERT(pointToSplit.IsInContentNode());
8090 // Split the header
8091 Result<SplitNodeResult, nsresult> splitResult =
8092 SplitNodeDeepWithTransaction(
8093 aHeadingElement, pointToSplit,
8094 SplitAtEdges::eAllowToCreateEmptyContainer);
8095 NS_WARNING_ASSERTION(
8096 splitResult.isOk(),
8097 "HTMLEditor::SplitNodeDeepWithTransaction(aHeadingElement, "
8098 "SplitAtEdges::eAllowToCreateEmptyContainer) failed");
8099 return splitResult;
8100 }();
8101 if (MOZ_UNLIKELY(splitHeadingResult.isErr())) {
8102 NS_WARNING("Failed to splitting aHeadingElement");
8103 return splitHeadingResult.propagateErr();
8105 SplitNodeResult unwrappedSplitHeadingResult = splitHeadingResult.unwrap();
8106 unwrappedSplitHeadingResult.IgnoreCaretPointSuggestion();
8107 if (MOZ_UNLIKELY(!unwrappedSplitHeadingResult.DidSplit())) {
8108 NS_WARNING(
8109 "HTMLEditor::SplitNodeDeepWithTransaction(SplitAtEdges::"
8110 "eAllowToCreateEmptyContainer) didn't split aHeadingElement");
8111 return Err(NS_ERROR_FAILURE);
8114 // If the left heading element is empty, put a padding <br> element for empty
8115 // last line into it.
8116 // FYI: leftHeadingElement is grabbed by unwrappedSplitHeadingResult so that
8117 // it's safe to access anytime.
8118 auto* const leftHeadingElement =
8119 unwrappedSplitHeadingResult.GetPreviousContentAs<Element>();
8120 MOZ_ASSERT(leftHeadingElement,
8121 "SplitNodeResult::GetPreviousContent() should return something if "
8122 "DidSplit() returns true");
8123 MOZ_DIAGNOSTIC_ASSERT(HTMLEditUtils::IsHeader(*leftHeadingElement));
8124 if (HTMLEditUtils::IsEmptyNode(
8125 *leftHeadingElement,
8126 {EmptyCheckOption::TreatSingleBRElementAsVisible,
8127 EmptyCheckOption::TreatNonEditableContentAsInvisible})) {
8128 Result<CreateElementResult, nsresult> insertPaddingBRElementResult =
8129 InsertPaddingBRElementForEmptyLastLineWithTransaction(
8130 EditorDOMPoint(leftHeadingElement, 0u));
8131 if (MOZ_UNLIKELY(insertPaddingBRElementResult.isErr())) {
8132 NS_WARNING(
8133 "HTMLEditor::InsertPaddingBRElementForEmptyLastLineWithTransaction("
8134 ") failed");
8135 return insertPaddingBRElementResult.propagateErr();
8137 insertPaddingBRElementResult.inspect().IgnoreCaretPointSuggestion();
8140 // Put caret at start of the right head element if it's not empty.
8141 auto* const rightHeadingElement =
8142 unwrappedSplitHeadingResult.GetNextContentAs<Element>();
8143 MOZ_ASSERT(rightHeadingElement,
8144 "SplitNodeResult::GetNextContent() should return something if "
8145 "DidSplit() returns true");
8146 if (!HTMLEditUtils::IsEmptyBlockElement(
8147 *rightHeadingElement,
8148 {EmptyCheckOption::TreatNonEditableContentAsInvisible},
8149 BlockInlineCheck::UseComputedDisplayOutsideStyle)) {
8150 return InsertParagraphResult(rightHeadingElement,
8151 EditorDOMPoint(rightHeadingElement, 0u));
8154 // If the right heading element is empty, delete it.
8155 // TODO: If we know the new heading element becomes empty, we stop spliting
8156 // the heading element.
8157 // MOZ_KnownLive(rightHeadingElement) because it's grabbed by
8158 // unwrappedSplitHeadingResult.
8159 nsresult rv = DeleteNodeWithTransaction(MOZ_KnownLive(*rightHeadingElement));
8160 if (NS_FAILED(rv)) {
8161 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
8162 return Err(rv);
8165 // Layout tells the caret to blink in a weird place if we don't place a
8166 // break after the header.
8167 // XXX This block is dead code unless the removed right heading element is
8168 // reconnected by a mutation event listener. This is a regression of
8169 // bug 1405751:
8170 // https://searchfox.org/mozilla-central/diff/879f3317d1331818718e18776caa47be7f426a22/editor/libeditor/HTMLEditRules.cpp#6389
8171 // However, the traditional behavior is different from the other browsers.
8172 // Chrome creates new paragraph in this case. Therefore, we should just
8173 // drop this block in a follow up bug.
8174 if (rightHeadingElement->GetNextSibling()) {
8175 // XXX Ignoring non-editable <br> element here is odd because non-editable
8176 // <br> elements also work as <br> from point of view of layout.
8177 nsIContent* nextEditableSibling =
8178 HTMLEditUtils::GetNextSibling(*rightHeadingElement->GetNextSibling(),
8179 {WalkTreeOption::IgnoreNonEditableNode});
8180 if (nextEditableSibling &&
8181 nextEditableSibling->IsHTMLElement(nsGkAtoms::br)) {
8182 auto afterEditableBRElement = EditorDOMPoint::After(*nextEditableSibling);
8183 if (NS_WARN_IF(!afterEditableBRElement.IsSet())) {
8184 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
8186 // Put caret at the <br> element.
8187 return InsertParagraphResult::NotHandled(
8188 std::move(afterEditableBRElement));
8192 if (MOZ_UNLIKELY(!leftHeadingElement->IsInComposedDoc())) {
8193 NS_WARNING("The left heading element was unexpectedly removed");
8194 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
8197 TopLevelEditSubActionDataRef().mCachedPendingStyles->Clear();
8198 mPendingStylesToApplyToNewContent->ClearAllStyles();
8200 // Create a paragraph if the right heading element is not followed by an
8201 // editable <br> element.
8202 nsStaticAtom& newParagraphTagName =
8203 &DefaultParagraphSeparatorTagName() == nsGkAtoms::br
8204 ? *nsGkAtoms::p
8205 : DefaultParagraphSeparatorTagName();
8206 // We want a wrapper element even if we separate with a <br>.
8207 // MOZ_KnownLive(newParagraphTagName) because it's available until shutdown.
8208 Result<CreateElementResult, nsresult> createNewParagraphElementResult =
8209 CreateAndInsertElement(WithTransaction::Yes,
8210 MOZ_KnownLive(newParagraphTagName),
8211 EditorDOMPoint::After(*leftHeadingElement),
8212 HTMLEditor::InsertNewBRElement);
8213 if (MOZ_UNLIKELY(createNewParagraphElementResult.isErr())) {
8214 NS_WARNING(
8215 "HTMLEditor::CreateAndInsertElement(WithTransaction::Yes) failed");
8216 return createNewParagraphElementResult.propagateErr();
8218 CreateElementResult unwrappedCreateNewParagraphElementResult =
8219 createNewParagraphElementResult.unwrap();
8220 // Put caret at the <br> element in the following paragraph.
8221 unwrappedCreateNewParagraphElementResult.IgnoreCaretPointSuggestion();
8222 MOZ_ASSERT(unwrappedCreateNewParagraphElementResult.GetNewNode());
8223 EditorDOMPoint pointToPutCaret(
8224 unwrappedCreateNewParagraphElementResult.GetNewNode(), 0u);
8225 return InsertParagraphResult(
8226 unwrappedCreateNewParagraphElementResult.UnwrapNewNode(),
8227 std::move(pointToPutCaret));
8230 Result<SplitNodeResult, nsresult> HTMLEditor::HandleInsertParagraphInParagraph(
8231 Element& aParentDivOrP, const EditorDOMPoint& aCandidatePointToSplit,
8232 const Element& aEditingHost) {
8233 MOZ_ASSERT(IsEditActionDataAvailable());
8234 MOZ_ASSERT(aCandidatePointToSplit.IsSetAndValid());
8236 // First, get a better split point to avoid to create a new empty link in the
8237 // right paragraph.
8238 EditorDOMPoint pointToSplit = [&]() {
8239 // We shouldn't create new anchor element which has non-empty href unless
8240 // splitting middle of it because we assume that users don't want to create
8241 // *same* anchor element across two or more paragraphs in most cases.
8242 // So, adjust selection start if it's edge of anchor element(s).
8243 // XXX We don't support white-space collapsing in these cases since it needs
8244 // some additional work with WhiteSpaceVisibilityKeeper but it's not
8245 // usual case. E.g., |<a href="foo"><b>foo []</b> </a>|
8246 if (aCandidatePointToSplit.IsStartOfContainer()) {
8247 EditorDOMPoint candidatePoint(aCandidatePointToSplit);
8248 for (nsIContent* container =
8249 aCandidatePointToSplit.GetContainerAs<nsIContent>();
8250 container && container != &aParentDivOrP;
8251 container = container->GetParent()) {
8252 if (HTMLEditUtils::IsLink(container)) {
8253 // Found link should be only in right node. So, we shouldn't split
8254 // it.
8255 candidatePoint.Set(container);
8256 // Even if we found an anchor element, don't break because DOM API
8257 // allows to nest anchor elements.
8259 // If the container is middle of its parent, stop adjusting split point.
8260 if (container->GetPreviousSibling()) {
8261 // XXX Should we check if previous sibling is visible content?
8262 // E.g., should we ignore comment node, invisible <br> element?
8263 break;
8266 return candidatePoint;
8269 // We also need to check if selection is at invisible <br> element at end
8270 // of an <a href="foo"> element because editor inserts a <br> element when
8271 // user types Enter key after a white-space which is at middle of
8272 // <a href="foo"> element and when setting selection at end of the element,
8273 // selection becomes referring the <br> element. We may need to change this
8274 // behavior later if it'd be standardized.
8275 if (aCandidatePointToSplit.IsEndOfContainer() ||
8276 aCandidatePointToSplit.IsBRElementAtEndOfContainer()) {
8277 // If there are 2 <br> elements, the first <br> element is visible. E.g.,
8278 // |<a href="foo"><b>boo[]<br></b><br></a>|, we should split the <a>
8279 // element. Otherwise, E.g., |<a href="foo"><b>boo[]<br></b></a>|,
8280 // we should not split the <a> element and ignore inline elements in it.
8281 bool foundBRElement =
8282 aCandidatePointToSplit.IsBRElementAtEndOfContainer();
8283 EditorDOMPoint candidatePoint(aCandidatePointToSplit);
8284 for (nsIContent* container =
8285 aCandidatePointToSplit.GetContainerAs<nsIContent>();
8286 container && container != &aParentDivOrP;
8287 container = container->GetParent()) {
8288 if (HTMLEditUtils::IsLink(container)) {
8289 // Found link should be only in left node. So, we shouldn't split it.
8290 candidatePoint.SetAfter(container);
8291 // Even if we found an anchor element, don't break because DOM API
8292 // allows to nest anchor elements.
8294 // If the container is middle of its parent, stop adjusting split point.
8295 if (nsIContent* nextSibling = container->GetNextSibling()) {
8296 if (foundBRElement) {
8297 // If we've already found a <br> element, we assume found node is
8298 // visible <br> or something other node.
8299 // XXX Should we check if non-text data node like comment?
8300 break;
8303 // XXX Should we check if non-text data node like comment?
8304 if (!nextSibling->IsHTMLElement(nsGkAtoms::br)) {
8305 break;
8307 foundBRElement = true;
8310 return candidatePoint;
8312 return aCandidatePointToSplit;
8313 }();
8315 const bool createNewParagraph = GetReturnInParagraphCreatesNewParagraph();
8316 RefPtr<HTMLBRElement> brElement;
8317 if (createNewParagraph && pointToSplit.GetContainer() == &aParentDivOrP) {
8318 // We are try to split only the current paragraph. Therefore, we don't need
8319 // to create new <br> elements around it (if left and/or right paragraph
8320 // becomes empty, it'll be treated by SplitParagraphWithTransaction().
8321 brElement = nullptr;
8322 } else if (pointToSplit.IsInTextNode()) {
8323 if (pointToSplit.IsStartOfContainer()) {
8324 // If we're splitting the paragraph at start of a text node and there is
8325 // no preceding visible <br> element, we need to create a <br> element to
8326 // keep the inline elements containing this text node.
8327 // TODO: If the parent of the text node is the splitting paragraph,
8328 // obviously we don't need to do this because empty paragraphs will
8329 // be treated by SplitParagraphWithTransaction(). In this case, we
8330 // just need to update pointToSplit for using the same path as the
8331 // previous `if` block.
8332 brElement =
8333 HTMLBRElement::FromNodeOrNull(HTMLEditUtils::GetPreviousSibling(
8334 *pointToSplit.ContainerAs<Text>(),
8335 {WalkTreeOption::IgnoreNonEditableNode}));
8336 if (!brElement || HTMLEditUtils::IsInvisibleBRElement(*brElement) ||
8337 EditorUtils::IsPaddingBRElementForEmptyLastLine(*brElement)) {
8338 // If insertParagraph does not create a new paragraph, default to
8339 // insertLineBreak.
8340 if (!createNewParagraph) {
8341 return SplitNodeResult::NotHandled(pointToSplit);
8343 const EditorDOMPoint pointToInsertBR = pointToSplit.ParentPoint();
8344 MOZ_ASSERT(pointToInsertBR.IsSet());
8345 Result<CreateElementResult, nsresult> insertBRElementResult =
8346 InsertBRElement(WithTransaction::Yes, pointToInsertBR);
8347 if (MOZ_UNLIKELY(insertBRElementResult.isErr())) {
8348 NS_WARNING(
8349 "HTMLEditor::InsertBRElement(WithTransaction::Yes) failed");
8350 return insertBRElementResult.propagateErr();
8352 // We'll collapse `Selection` to the place suggested by
8353 // SplitParagraphWithTransaction.
8354 insertBRElementResult.inspect().IgnoreCaretPointSuggestion();
8355 brElement = HTMLBRElement::FromNodeOrNull(
8356 insertBRElementResult.inspect().GetNewNode());
8358 } else if (pointToSplit.IsEndOfContainer()) {
8359 // If we're splitting the paragraph at end of a text node and there is not
8360 // following visible <br> element, we need to create a <br> element after
8361 // the text node to make current style specified by parent inline elements
8362 // keep in the right paragraph.
8363 // TODO: Same as above, we don't need to do this if the text node is a
8364 // direct child of the paragraph. For using the simplest path, we
8365 // just need to update `pointToSplit` in the case.
8366 brElement = HTMLBRElement::FromNodeOrNull(HTMLEditUtils::GetNextSibling(
8367 *pointToSplit.ContainerAs<Text>(),
8368 {WalkTreeOption::IgnoreNonEditableNode}));
8369 if (!brElement || HTMLEditUtils::IsInvisibleBRElement(*brElement) ||
8370 EditorUtils::IsPaddingBRElementForEmptyLastLine(*brElement)) {
8371 // If insertParagraph does not create a new paragraph, default to
8372 // insertLineBreak.
8373 if (!createNewParagraph) {
8374 return SplitNodeResult::NotHandled(pointToSplit);
8376 const auto pointToInsertBR =
8377 EditorDOMPoint::After(*pointToSplit.ContainerAs<Text>());
8378 MOZ_ASSERT(pointToInsertBR.IsSet());
8379 Result<CreateElementResult, nsresult> insertBRElementResult =
8380 InsertBRElement(WithTransaction::Yes, pointToInsertBR);
8381 if (MOZ_UNLIKELY(insertBRElementResult.isErr())) {
8382 NS_WARNING(
8383 "HTMLEditor::InsertBRElement(WithTransaction::Yes) failed");
8384 return insertBRElementResult.propagateErr();
8386 // We'll collapse `Selection` to the place suggested by
8387 // SplitParagraphWithTransaction.
8388 insertBRElementResult.inspect().IgnoreCaretPointSuggestion();
8389 brElement = HTMLBRElement::FromNodeOrNull(
8390 insertBRElementResult.inspect().GetNewNode());
8392 } else {
8393 // If insertParagraph does not create a new paragraph, default to
8394 // insertLineBreak.
8395 if (!createNewParagraph) {
8396 return SplitNodeResult::NotHandled(pointToSplit);
8399 // If we're splitting the paragraph at middle of a text node, we should
8400 // split the text node here and put a <br> element next to the left text
8401 // node.
8402 // XXX Why? I think that this should be handled in
8403 // SplitParagraphWithTransaction() directly because I don't find
8404 // the necessary case of the <br> element.
8406 // XXX We split a text node here if caret is middle of it to insert
8407 // <br> element **before** splitting aParentDivOrP. Then, if
8408 // the <br> element becomes unnecessary, it'll be removed again.
8409 // So this does much more complicated things than what we want to
8410 // do here. We should handle this case separately to make the code
8411 // much simpler.
8413 // Normalize collapsible white-spaces around the split point to keep
8414 // them visible after the split. Note that this does not touch
8415 // selection because of using AutoTransactionsConserveSelection in
8416 // WhiteSpaceVisibilityKeeper::ReplaceTextAndRemoveEmptyTextNodes().
8417 Result<EditorDOMPoint, nsresult> pointToSplitOrError =
8418 WhiteSpaceVisibilityKeeper::PrepareToSplitBlockElement(
8419 *this, pointToSplit, aParentDivOrP);
8420 if (NS_WARN_IF(Destroyed())) {
8421 return Err(NS_ERROR_EDITOR_DESTROYED);
8423 if (MOZ_UNLIKELY(pointToSplitOrError.isErr())) {
8424 NS_WARNING(
8425 "WhiteSpaceVisibilityKeeper::PrepareToSplitBlockElement() "
8426 "failed");
8427 return pointToSplitOrError.propagateErr();
8429 MOZ_ASSERT(pointToSplitOrError.inspect().IsSetAndValid());
8430 if (pointToSplitOrError.inspect().IsSet()) {
8431 pointToSplit = pointToSplitOrError.unwrap();
8433 Result<SplitNodeResult, nsresult> splitParentDivOrPResult =
8434 SplitNodeWithTransaction(pointToSplit);
8435 if (MOZ_UNLIKELY(splitParentDivOrPResult.isErr())) {
8436 NS_WARNING("HTMLEditor::SplitNodeWithTransaction() failed");
8437 return splitParentDivOrPResult;
8439 // We'll collapse `Selection` to the place suggested by
8440 // SplitParagraphWithTransaction.
8441 splitParentDivOrPResult.inspect().IgnoreCaretPointSuggestion();
8443 pointToSplit.SetToEndOf(
8444 splitParentDivOrPResult.inspect().GetPreviousContent());
8445 if (NS_WARN_IF(!pointToSplit.IsInContentNode())) {
8446 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
8449 // We need to put new <br> after the left node if given node was split
8450 // above.
8451 const auto pointToInsertBR =
8452 EditorDOMPoint::After(*pointToSplit.ContainerAs<nsIContent>());
8453 MOZ_ASSERT(pointToInsertBR.IsSet());
8454 Result<CreateElementResult, nsresult> insertBRElementResult =
8455 InsertBRElement(WithTransaction::Yes, pointToInsertBR);
8456 if (MOZ_UNLIKELY(insertBRElementResult.isErr())) {
8457 NS_WARNING("HTMLEditor::InsertBRElement(WithTransaction::Yes) failed");
8458 return insertBRElementResult.propagateErr();
8460 // We'll collapse `Selection` to the place suggested by
8461 // SplitParagraphWithTransaction.
8462 insertBRElementResult.inspect().IgnoreCaretPointSuggestion();
8463 brElement = HTMLBRElement::FromNodeOrNull(
8464 insertBRElementResult.inspect().GetNewNode());
8466 } else {
8467 // If we're splitting in a child element of the paragraph, and there is no
8468 // <br> element around it, we should insert a <br> element at the split
8469 // point and keep splitting the paragraph after the new <br> element.
8470 // XXX Why? We probably need to do this if we're splitting in an inline
8471 // element which and whose parents provide some styles, we should put
8472 // the <br> element for making a placeholder in the left paragraph for
8473 // moving to the caret, but I think that this could be handled in fewer
8474 // cases than this.
8475 brElement = HTMLBRElement::FromNodeOrNull(HTMLEditUtils::GetPreviousContent(
8476 pointToSplit, {WalkTreeOption::IgnoreNonEditableNode},
8477 BlockInlineCheck::Unused, &aEditingHost));
8478 if (!brElement || HTMLEditUtils::IsInvisibleBRElement(*brElement) ||
8479 EditorUtils::IsPaddingBRElementForEmptyLastLine(*brElement)) {
8480 // is there a BR after it?
8481 brElement = HTMLBRElement::FromNodeOrNull(HTMLEditUtils::GetNextContent(
8482 pointToSplit, {WalkTreeOption::IgnoreNonEditableNode},
8483 BlockInlineCheck::Unused, &aEditingHost));
8484 if (!brElement || HTMLEditUtils::IsInvisibleBRElement(*brElement) ||
8485 EditorUtils::IsPaddingBRElementForEmptyLastLine(*brElement)) {
8486 // If insertParagraph does not create a new paragraph, default to
8487 // insertLineBreak.
8488 if (!createNewParagraph) {
8489 return SplitNodeResult::NotHandled(pointToSplit);
8491 Result<CreateElementResult, nsresult> insertBRElementResult =
8492 InsertBRElement(WithTransaction::Yes, pointToSplit);
8493 if (MOZ_UNLIKELY(insertBRElementResult.isErr())) {
8494 NS_WARNING(
8495 "HTMLEditor::InsertBRElement(WithTransaction::Yes) failed");
8496 return insertBRElementResult.propagateErr();
8498 // We'll collapse `Selection` to the place suggested by
8499 // SplitParagraphWithTransaction.
8500 insertBRElementResult.inspect().IgnoreCaretPointSuggestion();
8501 brElement = HTMLBRElement::FromNodeOrNull(
8502 insertBRElementResult.inspect().GetNewNode());
8503 // We split the parent after the <br>.
8504 pointToSplit.SetAfter(brElement);
8505 if (NS_WARN_IF(!pointToSplit.IsSet())) {
8506 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
8512 Result<SplitNodeResult, nsresult> splitParagraphResult =
8513 SplitParagraphWithTransaction(aParentDivOrP, pointToSplit, brElement);
8514 if (MOZ_UNLIKELY(splitParagraphResult.isErr())) {
8515 NS_WARNING("HTMLEditor::SplitParagraphWithTransaction() failed");
8516 return splitParagraphResult;
8518 if (MOZ_UNLIKELY(!splitParagraphResult.inspect().DidSplit())) {
8519 NS_WARNING(
8520 "HTMLEditor::SplitParagraphWithTransaction() didn't split the "
8521 "paragraph");
8522 splitParagraphResult.inspect().IgnoreCaretPointSuggestion();
8523 return Err(NS_ERROR_FAILURE);
8525 MOZ_ASSERT(splitParagraphResult.inspect().Handled());
8526 return splitParagraphResult;
8529 Result<SplitNodeResult, nsresult> HTMLEditor::SplitParagraphWithTransaction(
8530 Element& aParentDivOrP, const EditorDOMPoint& aStartOfRightNode,
8531 HTMLBRElement* aMayBecomeVisibleBRElement) {
8532 MOZ_ASSERT(IsEditActionDataAvailable());
8534 Result<EditorDOMPoint, nsresult> preparationResult =
8535 WhiteSpaceVisibilityKeeper::PrepareToSplitBlockElement(
8536 *this, aStartOfRightNode, aParentDivOrP);
8537 if (MOZ_UNLIKELY(preparationResult.isErr())) {
8538 NS_WARNING(
8539 "WhiteSpaceVisibilityKeeper::PrepareToSplitBlockElement() failed");
8540 return preparationResult.propagateErr();
8542 EditorDOMPoint pointToSplit = preparationResult.unwrap();
8543 MOZ_ASSERT(pointToSplit.IsInContentNode());
8545 // Split the paragraph.
8546 Result<SplitNodeResult, nsresult> splitDivOrPResult =
8547 SplitNodeDeepWithTransaction(aParentDivOrP, pointToSplit,
8548 SplitAtEdges::eAllowToCreateEmptyContainer);
8549 if (MOZ_UNLIKELY(splitDivOrPResult.isErr())) {
8550 NS_WARNING("HTMLEditor::SplitNodeDeepWithTransaction() failed");
8551 return splitDivOrPResult;
8553 SplitNodeResult unwrappedSplitDivOrPResult = splitDivOrPResult.unwrap();
8554 if (MOZ_UNLIKELY(!unwrappedSplitDivOrPResult.DidSplit())) {
8555 NS_WARNING(
8556 "HTMLEditor::SplitNodeDeepWithTransaction() didn't split any nodes");
8557 return unwrappedSplitDivOrPResult;
8560 // We'll compute caret suggestion later. So the simple result is not needed.
8561 unwrappedSplitDivOrPResult.IgnoreCaretPointSuggestion();
8563 auto* const leftDivOrParagraphElement =
8564 unwrappedSplitDivOrPResult.GetPreviousContentAs<Element>();
8565 MOZ_ASSERT(leftDivOrParagraphElement,
8566 "SplitNodeResult::GetPreviousContent() should return something if "
8567 "DidSplit() returns true");
8568 auto* const rightDivOrParagraphElement =
8569 unwrappedSplitDivOrPResult.GetNextContentAs<Element>();
8570 MOZ_ASSERT(rightDivOrParagraphElement,
8571 "SplitNodeResult::GetNextContent() should return something if "
8572 "DidSplit() returns true");
8574 // Get rid of the break, if it is visible (otherwise it may be needed to
8575 // prevent an empty p).
8576 if (aMayBecomeVisibleBRElement &&
8577 HTMLEditUtils::IsVisibleBRElement(*aMayBecomeVisibleBRElement)) {
8578 nsresult rv = DeleteNodeWithTransaction(*aMayBecomeVisibleBRElement);
8579 if (NS_FAILED(rv)) {
8580 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
8581 return Err(rv);
8585 // Remove ID attribute on the paragraph from the right node.
8586 // MOZ_KnownLive(rightDivOrParagraphElement) because it's grabbed by
8587 // unwrappedSplitDivOrPResult.
8588 nsresult rv = RemoveAttributeWithTransaction(
8589 MOZ_KnownLive(*rightDivOrParagraphElement), *nsGkAtoms::id);
8590 if (NS_FAILED(rv)) {
8591 NS_WARNING(
8592 "EditorBase::RemoveAttributeWithTransaction(nsGkAtoms::id) failed");
8593 return Err(rv);
8596 // We need to ensure to both paragraphs visible even if they are empty.
8597 // However, padding <br> element for empty last line isn't useful in this
8598 // case because it'll be ignored by PlaintextSerializer. Additionally,
8599 // it'll be exposed as <br> with Element.innerHTML. Therefore, we can use
8600 // normal <br> elements for placeholder in this case. Note that Chromium
8601 // also behaves so.
8602 auto InsertBRElementIfEmptyBlockElement =
8603 [&](Element& aElement) MOZ_CAN_RUN_SCRIPT {
8604 if (!HTMLEditUtils::IsBlockElement(
8605 aElement, BlockInlineCheck::UseComputedDisplayStyle)) {
8606 return NS_OK;
8609 if (!HTMLEditUtils::IsEmptyNode(
8610 aElement, {EmptyCheckOption::TreatSingleBRElementAsVisible})) {
8611 return NS_OK;
8614 // XXX: Probably, we should use
8615 // InsertPaddingBRElementForEmptyLastLineWithTransaction here, and
8616 // if there are some empty inline container, we should put the <br>
8617 // into the last one.
8618 Result<CreateElementResult, nsresult> insertBRElementResult =
8619 InsertBRElement(WithTransaction::Yes,
8620 EditorDOMPoint(&aElement, 0u));
8621 if (MOZ_UNLIKELY(insertBRElementResult.isErr())) {
8622 NS_WARNING(
8623 "HTMLEditor::InsertBRElement(WithTransaction::Yes) failed");
8624 return insertBRElementResult.unwrapErr();
8626 // After this is called twice, we'll compute new caret position.
8627 // Therefore, we don't need to update selection here.
8628 insertBRElementResult.inspect().IgnoreCaretPointSuggestion();
8629 return NS_OK;
8632 // MOZ_KnownLive(leftDivOrParagraphElement) because it's grabbed by
8633 // splitDivOrResult.
8634 rv = InsertBRElementIfEmptyBlockElement(
8635 MOZ_KnownLive(*leftDivOrParagraphElement));
8636 if (NS_FAILED(rv)) {
8637 NS_WARNING(
8638 "InsertBRElementIfEmptyBlockElement(leftDivOrParagraphElement) failed");
8639 return Err(rv);
8642 if (HTMLEditUtils::IsEmptyNode(*rightDivOrParagraphElement)) {
8643 // If the right paragraph is empty, it might have an empty inline element
8644 // (which may contain other empty inline containers) and optionally a <br>
8645 // element which may not be in the deepest inline element.
8646 const RefPtr<Element> deepestInlineContainerElement =
8647 [](const Element& aBlockElement) {
8648 Element* result = nullptr;
8649 for (Element* maybeDeepestInlineContainer =
8650 Element::FromNodeOrNull(aBlockElement.GetFirstChild());
8651 maybeDeepestInlineContainer &&
8652 HTMLEditUtils::IsInlineContent(
8653 *maybeDeepestInlineContainer,
8654 BlockInlineCheck::UseComputedDisplayStyle) &&
8655 HTMLEditUtils::IsContainerNode(*maybeDeepestInlineContainer);
8656 maybeDeepestInlineContainer =
8657 maybeDeepestInlineContainer->GetFirstElementChild()) {
8658 result = maybeDeepestInlineContainer;
8660 return result;
8661 }(*rightDivOrParagraphElement);
8662 if (deepestInlineContainerElement) {
8663 RefPtr<HTMLBRElement> brElement =
8664 HTMLEditUtils::GetFirstBRElement(*rightDivOrParagraphElement);
8665 // If there is a <br> element and it is in the deepest inline container,
8666 // we need to do nothing anymore. Let's suggest caret position as at the
8667 // <br>.
8668 if (brElement &&
8669 brElement->GetParentNode() == deepestInlineContainerElement) {
8670 brElement->SetFlags(NS_PADDING_FOR_EMPTY_LAST_LINE);
8671 return SplitNodeResult(std::move(unwrappedSplitDivOrPResult),
8672 EditorDOMPoint(brElement));
8674 // Otherwise, we should put a padding <br> element into the deepest inline
8675 // container and then, existing <br> element (if there is) becomes
8676 // unnecessary.
8677 if (brElement) {
8678 nsresult rv = DeleteNodeWithTransaction(*brElement);
8679 if (NS_FAILED(rv)) {
8680 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
8681 return Err(rv);
8684 Result<CreateElementResult, nsresult> insertPaddingBRElementResult =
8685 InsertPaddingBRElementForEmptyLastLineWithTransaction(
8686 EditorDOMPoint::AtEndOf(deepestInlineContainerElement));
8687 if (MOZ_UNLIKELY(insertPaddingBRElementResult.isErr())) {
8688 NS_WARNING(
8689 "HTMLEditor::"
8690 "InsertPaddingBRElementForEmptyLastLineWithTransaction() failed");
8691 return insertPaddingBRElementResult.propagateErr();
8693 insertPaddingBRElementResult.inspect().IgnoreCaretPointSuggestion();
8694 return SplitNodeResult(
8695 std::move(unwrappedSplitDivOrPResult),
8696 EditorDOMPoint(insertPaddingBRElementResult.inspect().GetNewNode()));
8699 // If there is no inline container elements, we just need to make the
8700 // right paragraph visible.
8701 nsresult rv = InsertBRElementIfEmptyBlockElement(
8702 MOZ_KnownLive(*rightDivOrParagraphElement));
8703 if (NS_FAILED(rv)) {
8704 NS_WARNING(
8705 "InsertBRElementIfEmptyBlockElement(rightDivOrParagraphElement) "
8706 "failed");
8707 return Err(rv);
8711 // Let's put caret at start of the first leaf container.
8712 nsIContent* child = HTMLEditUtils::GetFirstLeafContent(
8713 *rightDivOrParagraphElement, {LeafNodeType::LeafNodeOrChildBlock},
8714 BlockInlineCheck::UseComputedDisplayStyle);
8715 if (MOZ_UNLIKELY(!child)) {
8716 return SplitNodeResult(std::move(unwrappedSplitDivOrPResult),
8717 EditorDOMPoint(rightDivOrParagraphElement, 0u));
8719 return child->IsText() || HTMLEditUtils::IsContainerNode(*child)
8720 ? SplitNodeResult(std::move(unwrappedSplitDivOrPResult),
8721 EditorDOMPoint(child, 0u))
8722 : SplitNodeResult(std::move(unwrappedSplitDivOrPResult),
8723 EditorDOMPoint(child));
8726 Result<InsertParagraphResult, nsresult>
8727 HTMLEditor::HandleInsertParagraphInListItemElement(
8728 Element& aListItemElement, const EditorDOMPoint& aPointToSplit,
8729 const Element& aEditingHost) {
8730 MOZ_ASSERT(IsEditActionDataAvailable());
8731 MOZ_ASSERT(HTMLEditUtils::IsListItem(&aListItemElement));
8733 // If aListItemElement is empty, then we want to outdent its content.
8734 if (&aEditingHost != aListItemElement.GetParentElement() &&
8735 HTMLEditUtils::IsEmptyBlockElement(
8736 aListItemElement,
8737 {EmptyCheckOption::TreatNonEditableContentAsInvisible},
8738 BlockInlineCheck::UseComputedDisplayOutsideStyle)) {
8739 RefPtr<Element> leftListElement = aListItemElement.GetParentElement();
8740 // If the given list item element is not the last list item element of
8741 // its parent nor not followed by sub list elements, split the parent
8742 // before it.
8743 if (!HTMLEditUtils::IsLastChild(aListItemElement,
8744 {WalkTreeOption::IgnoreNonEditableNode})) {
8745 Result<SplitNodeResult, nsresult> splitListItemParentResult =
8746 SplitNodeWithTransaction(EditorDOMPoint(&aListItemElement));
8747 if (MOZ_UNLIKELY(splitListItemParentResult.isErr())) {
8748 NS_WARNING("HTMLEditor::SplitNodeWithTransaction() failed");
8749 return splitListItemParentResult.propagateErr();
8751 SplitNodeResult unwrappedSplitListItemParentResult =
8752 splitListItemParentResult.unwrap();
8753 if (MOZ_UNLIKELY(!unwrappedSplitListItemParentResult.DidSplit())) {
8754 NS_WARNING(
8755 "HTMLEditor::SplitNodeWithTransaction() didn't split the parent of "
8756 "aListItemElement");
8757 MOZ_ASSERT(
8758 !unwrappedSplitListItemParentResult.HasCaretPointSuggestion());
8759 return Err(NS_ERROR_FAILURE);
8761 unwrappedSplitListItemParentResult.IgnoreCaretPointSuggestion();
8762 leftListElement =
8763 unwrappedSplitListItemParentResult.GetPreviousContentAs<Element>();
8764 MOZ_DIAGNOSTIC_ASSERT(leftListElement);
8767 auto afterLeftListElement = EditorDOMPoint::After(leftListElement);
8768 if (MOZ_UNLIKELY(!afterLeftListElement.IsSet())) {
8769 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
8772 // If aListItemElement is in an invalid sub-list element, move it into
8773 // the grand parent list element in order to outdent.
8774 if (HTMLEditUtils::IsAnyListElement(afterLeftListElement.GetContainer())) {
8775 Result<MoveNodeResult, nsresult> moveListItemElementResult =
8776 MoveNodeWithTransaction(aListItemElement, afterLeftListElement);
8777 if (MOZ_UNLIKELY(moveListItemElementResult.isErr())) {
8778 NS_WARNING("HTMLEditor::MoveNodeWithTransaction() failed");
8779 return moveListItemElementResult.propagateErr();
8781 moveListItemElementResult.inspect().IgnoreCaretPointSuggestion();
8782 return InsertParagraphResult(&aListItemElement,
8783 EditorDOMPoint(&aListItemElement, 0u));
8786 // Otherwise, replace the empty aListItemElement with a new paragraph.
8787 nsresult rv = DeleteNodeWithTransaction(aListItemElement);
8788 if (NS_FAILED(rv)) {
8789 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
8790 return Err(rv);
8792 nsStaticAtom& newParagraphTagName =
8793 &DefaultParagraphSeparatorTagName() == nsGkAtoms::br
8794 ? *nsGkAtoms::p
8795 : DefaultParagraphSeparatorTagName();
8796 // MOZ_KnownLive(newParagraphTagName) because it's available until shutdown.
8797 Result<CreateElementResult, nsresult> createNewParagraphElementResult =
8798 CreateAndInsertElement(
8799 WithTransaction::Yes, MOZ_KnownLive(newParagraphTagName),
8800 afterLeftListElement, HTMLEditor::InsertNewBRElement);
8801 if (MOZ_UNLIKELY(createNewParagraphElementResult.isErr())) {
8802 NS_WARNING(
8803 "HTMLEditor::CreateAndInsertElement(WithTransaction::Yes) failed");
8804 return createNewParagraphElementResult.propagateErr();
8806 createNewParagraphElementResult.inspect().IgnoreCaretPointSuggestion();
8807 MOZ_ASSERT(createNewParagraphElementResult.inspect().GetNewNode());
8808 EditorDOMPoint pointToPutCaret(
8809 createNewParagraphElementResult.inspect().GetNewNode(), 0u);
8810 return InsertParagraphResult(
8811 createNewParagraphElementResult.inspect().GetNewNode(),
8812 std::move(pointToPutCaret));
8815 // If aListItemElement has some content or aListItemElement is empty but it's
8816 // a child of editing host, we want a new list item at the same list level.
8817 // First, sort out white-spaces.
8818 Result<EditorDOMPoint, nsresult> preparationResult =
8819 WhiteSpaceVisibilityKeeper::PrepareToSplitBlockElement(
8820 *this, aPointToSplit, aListItemElement);
8821 if (preparationResult.isErr()) {
8822 NS_WARNING(
8823 "WhiteSpaceVisibilityKeeper::PrepareToSplitBlockElement() failed");
8824 return Err(preparationResult.unwrapErr());
8826 EditorDOMPoint pointToSplit = preparationResult.unwrap();
8827 MOZ_ASSERT(pointToSplit.IsInContentNode());
8829 // Now split the list item.
8830 Result<SplitNodeResult, nsresult> splitListItemResult =
8831 SplitNodeDeepWithTransaction(aListItemElement, pointToSplit,
8832 SplitAtEdges::eAllowToCreateEmptyContainer);
8833 if (MOZ_UNLIKELY(splitListItemResult.isErr())) {
8834 NS_WARNING("HTMLEditor::SplitNodeDeepWithTransaction() failed");
8835 return splitListItemResult.propagateErr();
8837 SplitNodeResult unwrappedSplitListItemElement = splitListItemResult.unwrap();
8838 unwrappedSplitListItemElement.IgnoreCaretPointSuggestion();
8839 if (MOZ_UNLIKELY(!aListItemElement.GetParent())) {
8840 NS_WARNING("Somebody disconnected the target listitem from the parent");
8841 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
8844 // If aListItemElement is not replaced, we should not do anything anymore.
8845 if (MOZ_UNLIKELY(!unwrappedSplitListItemElement.DidSplit()) ||
8846 NS_WARN_IF(!unwrappedSplitListItemElement.GetNewContentAs<Element>()) ||
8847 NS_WARN_IF(
8848 !unwrappedSplitListItemElement.GetOriginalContentAs<Element>())) {
8849 NS_WARNING("HTMLEditor::SplitNodeDeepWithTransaction() didn't split");
8850 return Err(NS_ERROR_FAILURE);
8853 // FYI: They are grabbed by unwrappedSplitListItemElement so that they are
8854 // known live
8855 // things.
8856 auto& leftListItemElement =
8857 *unwrappedSplitListItemElement.GetPreviousContentAs<Element>();
8858 auto& rightListItemElement =
8859 *unwrappedSplitListItemElement.GetNextContentAs<Element>();
8861 // Hack: until I can change the damaged doc range code back to being
8862 // extra-inclusive, I have to manually detect certain list items that may be
8863 // left empty.
8864 if (HTMLEditUtils::IsEmptyNode(
8865 leftListItemElement,
8866 {EmptyCheckOption::TreatSingleBRElementAsVisible,
8867 EmptyCheckOption::TreatNonEditableContentAsInvisible})) {
8868 Result<CreateElementResult, nsresult> insertPaddingBRElementResult =
8869 InsertPaddingBRElementForEmptyLastLineWithTransaction(
8870 EditorDOMPoint(&leftListItemElement, 0u));
8871 if (MOZ_UNLIKELY(insertPaddingBRElementResult.isErr())) {
8872 NS_WARNING(
8873 "HTMLEditor::InsertPaddingBRElementForEmptyLastLineWithTransaction("
8874 ") failed");
8875 return insertPaddingBRElementResult.propagateErr();
8877 // We're returning a candidate point to put caret so that we don't need to
8878 // update now.
8879 insertPaddingBRElementResult.inspect().IgnoreCaretPointSuggestion();
8880 return InsertParagraphResult(&rightListItemElement,
8881 EditorDOMPoint(&rightListItemElement, 0u));
8884 if (HTMLEditUtils::IsEmptyNode(
8885 rightListItemElement,
8886 {EmptyCheckOption::TreatNonEditableContentAsInvisible})) {
8887 // If aListItemElement is a <dd> or a <dt> and the right list item is empty
8888 // or a direct child of the editing host, replace it a new list item element
8889 // whose type is the other one.
8890 if (aListItemElement.IsAnyOfHTMLElements(nsGkAtoms::dd, nsGkAtoms::dt)) {
8891 nsStaticAtom& nextDefinitionListItemTagName =
8892 aListItemElement.IsHTMLElement(nsGkAtoms::dt) ? *nsGkAtoms::dd
8893 : *nsGkAtoms::dt;
8894 // MOZ_KnownLive(nextDefinitionListItemTagName) because it's available
8895 // until shutdown.
8896 Result<CreateElementResult, nsresult> createNewListItemElementResult =
8897 CreateAndInsertElement(WithTransaction::Yes,
8898 MOZ_KnownLive(nextDefinitionListItemTagName),
8899 EditorDOMPoint::After(rightListItemElement));
8900 if (MOZ_UNLIKELY(createNewListItemElementResult.isErr())) {
8901 NS_WARNING(
8902 "HTMLEditor::CreateAndInsertElement(WithTransaction::Yes) failed");
8903 return createNewListItemElementResult.propagateErr();
8905 CreateElementResult unwrappedCreateNewListItemElementResult =
8906 createNewListItemElementResult.unwrap();
8907 unwrappedCreateNewListItemElementResult.IgnoreCaretPointSuggestion();
8908 RefPtr<Element> newListItemElement =
8909 unwrappedCreateNewListItemElementResult.UnwrapNewNode();
8910 MOZ_ASSERT(newListItemElement);
8911 // MOZ_KnownLive(rightListItemElement) because it's grabbed by
8912 // unwrappedSplitListItemElement.
8913 nsresult rv =
8914 DeleteNodeWithTransaction(MOZ_KnownLive(rightListItemElement));
8915 if (NS_FAILED(rv)) {
8916 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
8917 return Err(rv);
8919 EditorDOMPoint pointToPutCaret(newListItemElement, 0u);
8920 return InsertParagraphResult(std::move(newListItemElement),
8921 std::move(pointToPutCaret));
8924 // If aListItemElement is a <li> and the right list item becomes empty or a
8925 // direct child of the editing host, copy all inline elements affecting to
8926 // the style at end of the left list item element to the right list item
8927 // element.
8928 // MOZ_KnownLive(leftListItemElement) and
8929 // MOZ_KnownLive(rightListItemElement) because they are grabbed by
8930 // unwrappedSplitListItemElement.
8931 Result<EditorDOMPoint, nsresult> pointToPutCaretOrError =
8932 CopyLastEditableChildStylesWithTransaction(
8933 MOZ_KnownLive(leftListItemElement),
8934 MOZ_KnownLive(rightListItemElement), aEditingHost);
8935 if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) {
8936 NS_WARNING(
8937 "HTMLEditor::CopyLastEditableChildStylesWithTransaction() failed");
8938 return pointToPutCaretOrError.propagateErr();
8940 return InsertParagraphResult(&rightListItemElement,
8941 pointToPutCaretOrError.unwrap());
8944 // If the right list item element is not empty, we need to consider where to
8945 // put caret in it. If it has non-container inline elements, <br> or <hr>, at
8946 // the element is proper position.
8947 WSScanResult forwardScanFromStartOfListItemResult =
8948 WSRunScanner::ScanNextVisibleNodeOrBlockBoundary(
8949 &aEditingHost, EditorRawDOMPoint(&rightListItemElement, 0u),
8950 BlockInlineCheck::UseComputedDisplayStyle);
8951 if (MOZ_UNLIKELY(forwardScanFromStartOfListItemResult.Failed())) {
8952 NS_WARNING("WSRunScanner::ScanNextVisibleNodeOrBlockBoundary() failed");
8953 return Err(NS_ERROR_FAILURE);
8955 if (forwardScanFromStartOfListItemResult.ReachedSpecialContent() ||
8956 forwardScanFromStartOfListItemResult.ReachedBRElement() ||
8957 forwardScanFromStartOfListItemResult.ReachedHRElement()) {
8958 auto atFoundElement =
8959 forwardScanFromStartOfListItemResult.PointAtContent<EditorDOMPoint>();
8960 if (NS_WARN_IF(!atFoundElement.IsSetAndValid())) {
8961 return Err(NS_ERROR_FAILURE);
8963 return InsertParagraphResult(&rightListItemElement,
8964 std::move(atFoundElement));
8967 // If we reached a block boundary (end of the list item or a child block),
8968 // let's put deepest start of the list item or the child block.
8969 if (forwardScanFromStartOfListItemResult.ReachedBlockBoundary()) {
8970 return InsertParagraphResult(
8971 &rightListItemElement,
8972 HTMLEditUtils::GetDeepestEditableStartPointOf<EditorDOMPoint>(
8973 forwardScanFromStartOfListItemResult.GetContent()
8974 ? *forwardScanFromStartOfListItemResult.GetContent()
8975 : rightListItemElement));
8978 // Otherwise, return the point at first visible thing.
8979 // XXX This may be not meaningful position if it reached block element
8980 // in aListItemElement.
8981 return InsertParagraphResult(
8982 &rightListItemElement,
8983 forwardScanFromStartOfListItemResult.Point<EditorDOMPoint>());
8986 Result<CreateElementResult, nsresult>
8987 HTMLEditor::WrapContentsInBlockquoteElementsWithTransaction(
8988 const nsTArray<OwningNonNull<nsIContent>>& aArrayOfContents,
8989 const Element& aEditingHost) {
8990 MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable());
8992 // The idea here is to put the nodes into a minimal number of blockquotes.
8993 // When the user blockquotes something, they expect one blockquote. That
8994 // may not be possible (for instance, if they have two table cells selected,
8995 // you need two blockquotes inside the cells).
8996 RefPtr<Element> curBlock, blockElementToPutCaret;
8997 nsCOMPtr<nsINode> prevParent;
8999 EditorDOMPoint pointToPutCaret;
9000 for (auto& content : aArrayOfContents) {
9001 // If the node is a table element or list item, dive inside
9002 if (HTMLEditUtils::IsAnyTableElementButNotTable(content) ||
9003 HTMLEditUtils::IsListItem(content)) {
9004 // Forget any previous block
9005 curBlock = nullptr;
9006 // Recursion time
9007 AutoTArray<OwningNonNull<nsIContent>, 24> childContents;
9008 HTMLEditUtils::CollectAllChildren(*content, childContents);
9009 Result<CreateElementResult, nsresult>
9010 wrapChildrenInAnotherBlockquoteResult =
9011 WrapContentsInBlockquoteElementsWithTransaction(childContents,
9012 aEditingHost);
9013 if (MOZ_UNLIKELY(wrapChildrenInAnotherBlockquoteResult.isErr())) {
9014 NS_WARNING(
9015 "HTMLEditor::WrapContentsInBlockquoteElementsWithTransaction() "
9016 "failed");
9017 return wrapChildrenInAnotherBlockquoteResult;
9019 CreateElementResult unwrappedWrapChildrenInAnotherBlockquoteResult =
9020 wrapChildrenInAnotherBlockquoteResult.unwrap();
9021 unwrappedWrapChildrenInAnotherBlockquoteResult.MoveCaretPointTo(
9022 pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
9023 if (unwrappedWrapChildrenInAnotherBlockquoteResult.GetNewNode()) {
9024 blockElementToPutCaret =
9025 unwrappedWrapChildrenInAnotherBlockquoteResult.UnwrapNewNode();
9029 // If the node has different parent than previous node, further nodes in a
9030 // new parent
9031 if (prevParent) {
9032 if (prevParent != content->GetParentNode()) {
9033 // Forget any previous blockquote node we were using
9034 curBlock = nullptr;
9035 prevParent = content->GetParentNode();
9037 } else {
9038 prevParent = content->GetParentNode();
9041 // If no curBlock, make one
9042 if (!curBlock) {
9043 Result<CreateElementResult, nsresult> createNewBlockquoteElementResult =
9044 InsertElementWithSplittingAncestorsWithTransaction(
9045 *nsGkAtoms::blockquote, EditorDOMPoint(content),
9046 BRElementNextToSplitPoint::Keep, aEditingHost);
9047 if (MOZ_UNLIKELY(createNewBlockquoteElementResult.isErr())) {
9048 NS_WARNING(
9049 "HTMLEditor::InsertElementWithSplittingAncestorsWithTransaction("
9050 "nsGkAtoms::blockquote) failed");
9051 return createNewBlockquoteElementResult;
9053 CreateElementResult unwrappedCreateNewBlockquoteElementResult =
9054 createNewBlockquoteElementResult.unwrap();
9055 unwrappedCreateNewBlockquoteElementResult.MoveCaretPointTo(
9056 pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
9057 MOZ_ASSERT(unwrappedCreateNewBlockquoteElementResult.GetNewNode());
9058 blockElementToPutCaret =
9059 unwrappedCreateNewBlockquoteElementResult.GetNewNode();
9060 curBlock = unwrappedCreateNewBlockquoteElementResult.UnwrapNewNode();
9063 // MOZ_KnownLive because 'aArrayOfContents' is guaranteed to/ keep it alive.
9064 Result<MoveNodeResult, nsresult> moveNodeResult =
9065 MoveNodeToEndWithTransaction(MOZ_KnownLive(content), *curBlock);
9066 if (MOZ_UNLIKELY(moveNodeResult.isErr())) {
9067 NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed");
9068 return moveNodeResult.propagateErr();
9070 MoveNodeResult unwrappedMoveNodeResult = moveNodeResult.unwrap();
9071 unwrappedMoveNodeResult.MoveCaretPointTo(
9072 pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
9074 return blockElementToPutCaret
9075 ? CreateElementResult(std::move(blockElementToPutCaret),
9076 std::move(pointToPutCaret))
9077 : CreateElementResult::NotHandled(std::move(pointToPutCaret));
9080 Result<EditorDOMPoint, nsresult>
9081 HTMLEditor::RemoveBlockContainerElementsWithTransaction(
9082 const nsTArray<OwningNonNull<nsIContent>>& aArrayOfContents,
9083 FormatBlockMode aFormatBlockMode, BlockInlineCheck aBlockInlineCheck) {
9084 MOZ_ASSERT(IsEditActionDataAvailable());
9085 MOZ_ASSERT(aFormatBlockMode == FormatBlockMode::XULParagraphStateCommand);
9087 // Intent of this routine is to be used for converting to/from headers,
9088 // paragraphs, pre, and address. Those blocks that pretty much just contain
9089 // inline things...
9090 RefPtr<Element> blockElement;
9091 nsCOMPtr<nsIContent> firstContent, lastContent;
9092 EditorDOMPoint pointToPutCaret;
9093 for (const auto& content : aArrayOfContents) {
9094 // If the current node is a format element, remove it.
9095 if (HTMLEditUtils::IsFormatElementForParagraphStateCommand(content)) {
9096 // Process any partial progress saved
9097 if (blockElement) {
9098 Result<SplitRangeOffFromNodeResult, nsresult> unwrapBlockElementResult =
9099 RemoveBlockContainerElementWithTransactionBetween(
9100 *blockElement, *firstContent, *lastContent, aBlockInlineCheck);
9101 if (MOZ_UNLIKELY(unwrapBlockElementResult.isErr())) {
9102 NS_WARNING(
9103 "HTMLEditor::RemoveBlockContainerElementWithTransactionBetween() "
9104 "failed");
9105 return unwrapBlockElementResult.propagateErr();
9107 unwrapBlockElementResult.unwrap().MoveCaretPointTo(
9108 pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
9109 firstContent = lastContent = blockElement = nullptr;
9111 if (!EditorUtils::IsEditableContent(content, EditorType::HTML)) {
9112 continue;
9114 // Remove current block
9115 Result<EditorDOMPoint, nsresult> unwrapFormatBlockResult =
9116 RemoveBlockContainerWithTransaction(
9117 MOZ_KnownLive(*content->AsElement()));
9118 if (MOZ_UNLIKELY(unwrapFormatBlockResult.isErr())) {
9119 NS_WARNING("HTMLEditor::RemoveBlockContainerWithTransaction() failed");
9120 return unwrapFormatBlockResult;
9122 if (unwrapFormatBlockResult.inspect().IsSet()) {
9123 pointToPutCaret = unwrapFormatBlockResult.unwrap();
9125 continue;
9128 // XXX How about, <th>, <thead>, <tfoot>, <dt>, <dl>?
9129 if (content->IsAnyOfHTMLElements(
9130 nsGkAtoms::table, nsGkAtoms::tr, nsGkAtoms::tbody, nsGkAtoms::td,
9131 nsGkAtoms::li, nsGkAtoms::blockquote, nsGkAtoms::div) ||
9132 HTMLEditUtils::IsAnyListElement(content)) {
9133 // Process any partial progress saved
9134 if (blockElement) {
9135 Result<SplitRangeOffFromNodeResult, nsresult> unwrapBlockElementResult =
9136 RemoveBlockContainerElementWithTransactionBetween(
9137 *blockElement, *firstContent, *lastContent, aBlockInlineCheck);
9138 if (MOZ_UNLIKELY(unwrapBlockElementResult.isErr())) {
9139 NS_WARNING(
9140 "HTMLEditor::RemoveBlockContainerElementWithTransactionBetween() "
9141 "failed");
9142 return unwrapBlockElementResult.propagateErr();
9144 unwrapBlockElementResult.unwrap().MoveCaretPointTo(
9145 pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
9146 firstContent = lastContent = blockElement = nullptr;
9148 if (!EditorUtils::IsEditableContent(content, EditorType::HTML)) {
9149 continue;
9151 // Recursion time
9152 AutoTArray<OwningNonNull<nsIContent>, 24> childContents;
9153 HTMLEditUtils::CollectAllChildren(*content, childContents);
9154 Result<EditorDOMPoint, nsresult> removeBlockContainerElementsResult =
9155 RemoveBlockContainerElementsWithTransaction(
9156 childContents, aFormatBlockMode, aBlockInlineCheck);
9157 if (MOZ_UNLIKELY(removeBlockContainerElementsResult.isErr())) {
9158 NS_WARNING(
9159 "HTMLEditor::RemoveBlockContainerElementsWithTransaction() failed");
9160 return removeBlockContainerElementsResult;
9162 if (removeBlockContainerElementsResult.inspect().IsSet()) {
9163 pointToPutCaret = removeBlockContainerElementsResult.unwrap();
9165 continue;
9168 if (HTMLEditUtils::IsInlineContent(content, aBlockInlineCheck)) {
9169 if (blockElement) {
9170 // If so, is this node a descendant?
9171 if (EditorUtils::IsDescendantOf(*content, *blockElement)) {
9172 // Then we don't need to do anything different for this node
9173 lastContent = content;
9174 continue;
9176 // Otherwise, we have progressed beyond end of blockElement, so let's
9177 // handle it now. We need to remove the portion of blockElement that
9178 // contains [firstContent - lastContent].
9179 Result<SplitRangeOffFromNodeResult, nsresult> unwrapBlockElementResult =
9180 RemoveBlockContainerElementWithTransactionBetween(
9181 *blockElement, *firstContent, *lastContent, aBlockInlineCheck);
9182 if (MOZ_UNLIKELY(unwrapBlockElementResult.isErr())) {
9183 NS_WARNING(
9184 "HTMLEditor::RemoveBlockContainerElementWithTransactionBetween() "
9185 "failed");
9186 return unwrapBlockElementResult.propagateErr();
9188 unwrapBlockElementResult.unwrap().MoveCaretPointTo(
9189 pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
9190 firstContent = lastContent = blockElement = nullptr;
9191 // Fall out and handle content
9193 blockElement = HTMLEditUtils::GetAncestorElement(
9194 content, HTMLEditUtils::ClosestEditableBlockElement,
9195 aBlockInlineCheck);
9196 if (!blockElement ||
9197 !HTMLEditUtils::IsFormatElementForParagraphStateCommand(
9198 *blockElement) ||
9199 !HTMLEditUtils::IsRemovableNode(*blockElement)) {
9200 // Not a block kind that we care about.
9201 blockElement = nullptr;
9202 } else {
9203 firstContent = lastContent = content;
9205 continue;
9208 if (blockElement) {
9209 // Some node that is already sans block style. Skip over it and process
9210 // any partial progress saved.
9211 Result<SplitRangeOffFromNodeResult, nsresult> unwrapBlockElementResult =
9212 RemoveBlockContainerElementWithTransactionBetween(
9213 *blockElement, *firstContent, *lastContent, aBlockInlineCheck);
9214 if (MOZ_UNLIKELY(unwrapBlockElementResult.isErr())) {
9215 NS_WARNING(
9216 "HTMLEditor::RemoveBlockContainerElementWithTransactionBetween() "
9217 "failed");
9218 return unwrapBlockElementResult.propagateErr();
9220 unwrapBlockElementResult.unwrap().MoveCaretPointTo(
9221 pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
9222 firstContent = lastContent = blockElement = nullptr;
9223 continue;
9226 // Process any partial progress saved
9227 if (blockElement) {
9228 Result<SplitRangeOffFromNodeResult, nsresult> unwrapBlockElementResult =
9229 RemoveBlockContainerElementWithTransactionBetween(
9230 *blockElement, *firstContent, *lastContent, aBlockInlineCheck);
9231 if (MOZ_UNLIKELY(unwrapBlockElementResult.isErr())) {
9232 NS_WARNING(
9233 "HTMLEditor::RemoveBlockContainerElementWithTransactionBetween() "
9234 "failed");
9235 return unwrapBlockElementResult.propagateErr();
9237 unwrapBlockElementResult.unwrap().MoveCaretPointTo(
9238 pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
9239 firstContent = lastContent = blockElement = nullptr;
9241 return pointToPutCaret;
9244 Result<CreateElementResult, nsresult>
9245 HTMLEditor::CreateOrChangeFormatContainerElement(
9246 nsTArray<OwningNonNull<nsIContent>>& aArrayOfContents,
9247 const nsStaticAtom& aNewFormatTagName, FormatBlockMode aFormatBlockMode,
9248 const Element& aEditingHost) {
9249 MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable());
9251 // Intent of this routine is to be used for converting to/from headers,
9252 // paragraphs, pre, and address. Those blocks that pretty much just contain
9253 // inline things...
9254 RefPtr<Element> newBlock, curBlock, blockElementToPutCaret;
9255 // If we found a <br> element which should be moved into curBlock, this keeps
9256 // storing the <br> element after removing it from the tree.
9257 RefPtr<Element> pendingBRElementToMoveCurBlock;
9258 EditorDOMPoint pointToPutCaret;
9259 for (auto& content : aArrayOfContents) {
9260 EditorDOMPoint atContent(content);
9261 if (NS_WARN_IF(!atContent.IsInContentNode())) {
9262 // If given node has been removed from the document, let's ignore it
9263 // since the following code may need its parent replace it with new
9264 // block.
9265 curBlock = nullptr;
9266 newBlock = nullptr;
9267 pendingBRElementToMoveCurBlock = nullptr;
9268 continue;
9271 // Is it already the right kind of block, or an uneditable block?
9272 if (content->IsHTMLElement(&aNewFormatTagName) ||
9273 (!EditorUtils::IsEditableContent(content, EditorType::HTML) &&
9274 HTMLEditUtils::IsBlockElement(
9275 content, BlockInlineCheck::UseHTMLDefaultStyle))) {
9276 // Forget any previous block used for previous inline nodes
9277 curBlock = nullptr;
9278 pendingBRElementToMoveCurBlock = nullptr;
9279 // Do nothing to this block
9280 continue;
9283 // If content is a format element, replace it with a new block of correct
9284 // type.
9285 // XXX: pre can't hold everything the others can
9286 if (HTMLEditUtils::IsMozDiv(content) ||
9287 HTMLEditor::IsFormatElement(aFormatBlockMode, content)) {
9288 // Forget any previous block used for previous inline nodes
9289 curBlock = nullptr;
9290 pendingBRElementToMoveCurBlock = nullptr;
9291 RefPtr<Element> expectedContainerOfNewBlock =
9292 atContent.IsContainerHTMLElement(nsGkAtoms::dl) &&
9293 HTMLEditUtils::IsSplittableNode(
9294 *atContent.ContainerAs<Element>())
9295 ? atContent.GetContainerParentAs<Element>()
9296 : atContent.GetContainerAs<Element>();
9297 Result<CreateElementResult, nsresult> replaceWithNewBlockElementResult =
9298 ReplaceContainerAndCloneAttributesWithTransaction(
9299 MOZ_KnownLive(*content->AsElement()), aNewFormatTagName);
9300 if (MOZ_UNLIKELY(replaceWithNewBlockElementResult.isErr())) {
9301 NS_WARNING(
9302 "EditorBase::ReplaceContainerAndCloneAttributesWithTransaction() "
9303 "failed");
9304 return replaceWithNewBlockElementResult;
9306 CreateElementResult unwrappedReplaceWithNewBlockElementResult =
9307 replaceWithNewBlockElementResult.unwrap();
9308 // If the new block element was moved to different element or removed by
9309 // the web app via mutation event listener, we should stop handling this
9310 // action since we cannot handle each of a lot of edge cases.
9311 if (NS_WARN_IF(unwrappedReplaceWithNewBlockElementResult.GetNewNode()
9312 ->GetParentNode() != expectedContainerOfNewBlock)) {
9313 unwrappedReplaceWithNewBlockElementResult.IgnoreCaretPointSuggestion();
9314 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
9316 unwrappedReplaceWithNewBlockElementResult.MoveCaretPointTo(
9317 pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
9318 newBlock = unwrappedReplaceWithNewBlockElementResult.UnwrapNewNode();
9319 continue;
9322 if (HTMLEditUtils::IsTable(content) ||
9323 HTMLEditUtils::IsAnyListElement(content) ||
9324 content->IsAnyOfHTMLElements(nsGkAtoms::tbody, nsGkAtoms::tr,
9325 nsGkAtoms::td, nsGkAtoms::li,
9326 nsGkAtoms::blockquote, nsGkAtoms::div)) {
9327 // Forget any previous block used for previous inline nodes
9328 curBlock = nullptr;
9329 pendingBRElementToMoveCurBlock = nullptr;
9330 // Recursion time
9331 AutoTArray<OwningNonNull<nsIContent>, 24> childContents;
9332 HTMLEditUtils::CollectAllChildren(*content, childContents);
9333 if (!childContents.IsEmpty()) {
9334 Result<CreateElementResult, nsresult> wrapChildrenInBlockElementResult =
9335 CreateOrChangeFormatContainerElement(
9336 childContents, aNewFormatTagName, aFormatBlockMode,
9337 aEditingHost);
9338 if (MOZ_UNLIKELY(wrapChildrenInBlockElementResult.isErr())) {
9339 NS_WARNING(
9340 "HTMLEditor::CreateOrChangeFormatContainerElement() failed");
9341 return wrapChildrenInBlockElementResult;
9343 CreateElementResult unwrappedWrapChildrenInBlockElementResult =
9344 wrapChildrenInBlockElementResult.unwrap();
9345 unwrappedWrapChildrenInBlockElementResult.MoveCaretPointTo(
9346 pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
9347 if (unwrappedWrapChildrenInBlockElementResult.GetNewNode()) {
9348 blockElementToPutCaret =
9349 unwrappedWrapChildrenInBlockElementResult.UnwrapNewNode();
9351 continue;
9354 // Make sure we can put a block here
9355 Result<CreateElementResult, nsresult> createNewBlockElementResult =
9356 InsertElementWithSplittingAncestorsWithTransaction(
9357 aNewFormatTagName, atContent, BRElementNextToSplitPoint::Keep,
9358 aEditingHost);
9359 if (MOZ_UNLIKELY(createNewBlockElementResult.isErr())) {
9360 NS_WARNING(
9361 nsPrintfCString(
9362 "HTMLEditor::"
9363 "InsertElementWithSplittingAncestorsWithTransaction(%s) failed",
9364 nsAtomCString(&aNewFormatTagName).get())
9365 .get());
9366 return createNewBlockElementResult;
9368 CreateElementResult unwrappedCreateNewBlockElementResult =
9369 createNewBlockElementResult.unwrap();
9370 unwrappedCreateNewBlockElementResult.MoveCaretPointTo(
9371 pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
9372 MOZ_ASSERT(unwrappedCreateNewBlockElementResult.GetNewNode());
9373 blockElementToPutCaret =
9374 unwrappedCreateNewBlockElementResult.UnwrapNewNode();
9375 continue;
9378 if (content->IsHTMLElement(nsGkAtoms::br)) {
9379 if (curBlock) {
9380 if (aFormatBlockMode == FormatBlockMode::XULParagraphStateCommand) {
9381 // If the node is a break, we honor it by putting further nodes in a
9382 // new parent.
9384 // Forget any previous block used for previous inline nodes.
9385 curBlock = nullptr;
9386 pendingBRElementToMoveCurBlock = nullptr;
9387 } else {
9388 // If the node is a break, we need to move it into end of the curBlock
9389 // if we'll move following content into curBlock.
9390 pendingBRElementToMoveCurBlock = content->AsElement();
9392 // MOZ_KnownLive because 'aArrayOfContents' is guaranteed to keep it
9393 // alive.
9394 nsresult rv = DeleteNodeWithTransaction(MOZ_KnownLive(*content));
9395 if (NS_FAILED(rv)) {
9396 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
9397 return Err(rv);
9399 continue;
9402 // The break is the first (or even only) node we encountered. Create a
9403 // block for it.
9404 Result<CreateElementResult, nsresult> createNewBlockElementResult =
9405 InsertElementWithSplittingAncestorsWithTransaction(
9406 aNewFormatTagName, atContent, BRElementNextToSplitPoint::Keep,
9407 aEditingHost);
9408 if (MOZ_UNLIKELY(createNewBlockElementResult.isErr())) {
9409 NS_WARNING(nsPrintfCString("HTMLEditor::"
9410 "InsertElementWithSplittingAncestorsWith"
9411 "Transaction(%s) failed",
9412 nsAtomCString(&aNewFormatTagName).get())
9413 .get());
9414 return createNewBlockElementResult;
9416 CreateElementResult unwrappedCreateNewBlockElementResult =
9417 createNewBlockElementResult.unwrap();
9418 unwrappedCreateNewBlockElementResult.MoveCaretPointTo(
9419 pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
9420 RefPtr<Element> newBlockElement =
9421 unwrappedCreateNewBlockElementResult.UnwrapNewNode();
9422 MOZ_ASSERT(newBlockElement);
9423 blockElementToPutCaret = newBlockElement;
9424 // MOZ_KnownLive because 'aArrayOfContents' is guaranteed to keep it
9425 // alive.
9426 Result<MoveNodeResult, nsresult> moveNodeResult =
9427 MoveNodeToEndWithTransaction(MOZ_KnownLive(content),
9428 *newBlockElement);
9429 if (MOZ_UNLIKELY(moveNodeResult.isErr())) {
9430 NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed");
9431 return moveNodeResult.propagateErr();
9433 MoveNodeResult unwrappedMoveNodeResult = moveNodeResult.unwrap();
9434 unwrappedMoveNodeResult.MoveCaretPointTo(
9435 pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
9436 curBlock = std::move(newBlockElement);
9437 continue;
9440 if (HTMLEditUtils::IsInlineContent(content,
9441 BlockInlineCheck::UseHTMLDefaultStyle)) {
9442 // If content is inline, pull it into curBlock. Note: it's assumed that
9443 // consecutive inline nodes in aNodeArray are actually members of the
9444 // same block parent. This happens to be true now as a side effect of
9445 // how aNodeArray is constructed, but some additional logic should be
9446 // added here if that should change
9448 // If content is a non editable, drop it if we are going to <pre>.
9449 if (&aNewFormatTagName == nsGkAtoms::pre &&
9450 !EditorUtils::IsEditableContent(content, EditorType::HTML)) {
9451 // Do nothing to this block
9452 continue;
9455 // If no curBlock, make one
9456 if (!curBlock) {
9457 Result<CreateElementResult, nsresult> createNewBlockElementResult =
9458 InsertElementWithSplittingAncestorsWithTransaction(
9459 aNewFormatTagName, atContent, BRElementNextToSplitPoint::Keep,
9460 aEditingHost);
9461 if (MOZ_UNLIKELY(createNewBlockElementResult.isErr())) {
9462 NS_WARNING(nsPrintfCString("HTMLEditor::"
9463 "InsertElementWithSplittingAncestorsWith"
9464 "Transaction(%s) failed",
9465 nsAtomCString(&aNewFormatTagName).get())
9466 .get());
9467 return createNewBlockElementResult;
9469 CreateElementResult unwrappedCreateNewBlockElementResult =
9470 createNewBlockElementResult.unwrap();
9471 unwrappedCreateNewBlockElementResult.MoveCaretPointTo(
9472 pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
9473 MOZ_ASSERT(unwrappedCreateNewBlockElementResult.GetNewNode());
9474 blockElementToPutCaret =
9475 unwrappedCreateNewBlockElementResult.GetNewNode();
9476 curBlock = unwrappedCreateNewBlockElementResult.UnwrapNewNode();
9478 // Update container of content.
9479 atContent.Set(content);
9480 if (NS_WARN_IF(!atContent.IsSet())) {
9481 // This is possible due to mutation events, let's not assert
9482 return Err(NS_ERROR_UNEXPECTED);
9484 } else if (pendingBRElementToMoveCurBlock) {
9485 Result<CreateElementResult, nsresult> insertBRElementResult =
9486 InsertNodeWithTransaction<Element>(
9487 *pendingBRElementToMoveCurBlock,
9488 EditorDOMPoint::AtEndOf(*curBlock));
9489 if (MOZ_UNLIKELY(insertBRElementResult.isErr())) {
9490 NS_WARNING("EditorBase::InsertNodeWithTransaction<Element>() failed");
9491 return insertBRElementResult.propagateErr();
9493 insertBRElementResult.inspect().IgnoreCaretPointSuggestion();
9494 pendingBRElementToMoveCurBlock = nullptr;
9497 // XXX If content is a br, replace it with a return if going to <pre>
9499 // This is a continuation of some inline nodes that belong together in
9500 // the same block item. Use curBlock.
9502 // MOZ_KnownLive because 'aArrayOfContents' is guaranteed to keep it
9503 // alive. We could try to make that a rvalue ref and create a const array
9504 // on the stack here, but callers are passing in auto arrays, and we don't
9505 // want to introduce copies..
9506 Result<MoveNodeResult, nsresult> moveNodeResult =
9507 MoveNodeToEndWithTransaction(MOZ_KnownLive(content), *curBlock);
9508 if (MOZ_UNLIKELY(moveNodeResult.isErr())) {
9509 NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed");
9510 return moveNodeResult.propagateErr();
9512 MoveNodeResult unwrappedMoveNodeResult = moveNodeResult.unwrap();
9513 unwrappedMoveNodeResult.MoveCaretPointTo(
9514 pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
9517 return blockElementToPutCaret
9518 ? CreateElementResult(std::move(blockElementToPutCaret),
9519 std::move(pointToPutCaret))
9520 : CreateElementResult::NotHandled(std::move(pointToPutCaret));
9523 Result<SplitNodeResult, nsresult>
9524 HTMLEditor::MaybeSplitAncestorsForInsertWithTransaction(
9525 const nsAtom& aTag, const EditorDOMPoint& aStartOfDeepestRightNode,
9526 const Element& aEditingHost) {
9527 MOZ_ASSERT(IsEditActionDataAvailable());
9529 if (NS_WARN_IF(!aEditingHost.IsInComposedDoc())) {
9530 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
9533 if (NS_WARN_IF(!aStartOfDeepestRightNode.IsSet())) {
9534 return Err(NS_ERROR_INVALID_ARG);
9536 MOZ_ASSERT(aStartOfDeepestRightNode.IsSetAndValid());
9538 // The point must be descendant of editing host.
9539 // XXX Isn't it a valid case if it points a direct child of aEditingHost?
9540 if (NS_WARN_IF(
9541 !aStartOfDeepestRightNode.GetContainer()->IsInclusiveDescendantOf(
9542 &aEditingHost))) {
9543 return Err(NS_ERROR_INVALID_ARG);
9546 // Look for a node that can legally contain the tag.
9547 const EditorDOMPoint pointToInsert =
9548 HTMLEditUtils::GetInsertionPointInInclusiveAncestor(
9549 aTag, aStartOfDeepestRightNode, &aEditingHost);
9550 if (MOZ_UNLIKELY(!pointToInsert.IsSet())) {
9551 NS_WARNING(
9552 "HTMLEditor::MaybeSplitAncestorsForInsertWithTransaction() reached "
9553 "editing host");
9554 return Err(NS_ERROR_FAILURE);
9556 // If the point itself can contain the tag, we don't need to split any
9557 // ancestor nodes. In this case, we should return the given split point
9558 // as is.
9559 if (pointToInsert.GetContainer() == aStartOfDeepestRightNode.GetContainer()) {
9560 return SplitNodeResult::NotHandled(aStartOfDeepestRightNode);
9563 Result<SplitNodeResult, nsresult> splitNodeResult =
9564 SplitNodeDeepWithTransaction(MOZ_KnownLive(*pointToInsert.GetChild()),
9565 aStartOfDeepestRightNode,
9566 SplitAtEdges::eAllowToCreateEmptyContainer);
9567 NS_WARNING_ASSERTION(splitNodeResult.isOk(),
9568 "HTMLEditor::SplitNodeDeepWithTransaction(SplitAtEdges::"
9569 "eAllowToCreateEmptyContainer) failed");
9570 return splitNodeResult;
9573 Result<CreateElementResult, nsresult>
9574 HTMLEditor::InsertElementWithSplittingAncestorsWithTransaction(
9575 const nsAtom& aTagName, const EditorDOMPoint& aPointToInsert,
9576 BRElementNextToSplitPoint aBRElementNextToSplitPoint,
9577 const Element& aEditingHost,
9578 const InitializeInsertingElement& aInitializer) {
9579 MOZ_ASSERT(aPointToInsert.IsSetAndValid());
9581 const nsCOMPtr<nsIContent> childAtPointToInsert = aPointToInsert.GetChild();
9582 Result<SplitNodeResult, nsresult> splitNodeResult =
9583 MaybeSplitAncestorsForInsertWithTransaction(aTagName, aPointToInsert,
9584 aEditingHost);
9585 if (MOZ_UNLIKELY(splitNodeResult.isErr())) {
9586 NS_WARNING(
9587 "HTMLEditor::MaybeSplitAncestorsForInsertWithTransaction() failed");
9588 return splitNodeResult.propagateErr();
9590 SplitNodeResult unwrappedSplitNodeResult = splitNodeResult.unwrap();
9591 DebugOnly<bool> wasCaretPositionSuggestedAtSplit =
9592 unwrappedSplitNodeResult.HasCaretPointSuggestion();
9593 // We'll update selection below, and nobody touches selection until then.
9594 // Therefore, we don't need to touch selection here.
9595 unwrappedSplitNodeResult.IgnoreCaretPointSuggestion();
9597 // If current handling node has been moved from the container by a
9598 // mutation event listener when we need to do something more for it,
9599 // we should stop handling this action since we cannot handle each
9600 // edge case.
9601 if (childAtPointToInsert &&
9602 NS_WARN_IF(!childAtPointToInsert->IsInclusiveDescendantOf(
9603 unwrappedSplitNodeResult.DidSplit()
9604 ? unwrappedSplitNodeResult.GetNextContent()
9605 : aPointToInsert.GetContainer()))) {
9606 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
9609 auto splitPoint = unwrappedSplitNodeResult.AtSplitPoint<EditorDOMPoint>();
9610 if (aBRElementNextToSplitPoint == BRElementNextToSplitPoint::Delete) {
9611 // Consume a trailing br, if any. This is to keep an alignment from
9612 // creating extra lines, if possible.
9613 if (nsCOMPtr<nsIContent> maybeBRContent = HTMLEditUtils::GetNextContent(
9614 splitPoint,
9615 {WalkTreeOption::IgnoreNonEditableNode,
9616 WalkTreeOption::StopAtBlockBoundary},
9617 BlockInlineCheck::UseComputedDisplayOutsideStyle, &aEditingHost)) {
9618 if (maybeBRContent->IsHTMLElement(nsGkAtoms::br) &&
9619 splitPoint.GetChild()) {
9620 // Making use of html structure... if next node after where we are
9621 // putting our div is not a block, then the br we found is in same
9622 // block we are, so it's safe to consume it.
9623 if (nsIContent* nextEditableSibling = HTMLEditUtils::GetNextSibling(
9624 *splitPoint.GetChild(),
9625 {WalkTreeOption::IgnoreNonEditableNode})) {
9626 if (!HTMLEditUtils::IsBlockElement(
9627 *nextEditableSibling,
9628 BlockInlineCheck::UseComputedDisplayOutsideStyle)) {
9629 AutoEditorDOMPointChildInvalidator lockOffset(splitPoint);
9630 nsresult rv = DeleteNodeWithTransaction(*maybeBRContent);
9631 if (NS_FAILED(rv)) {
9632 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
9633 return Err(rv);
9641 Result<CreateElementResult, nsresult> createNewElementResult =
9642 CreateAndInsertElement(WithTransaction::Yes, aTagName, splitPoint,
9643 aInitializer);
9644 if (MOZ_UNLIKELY(createNewElementResult.isErr())) {
9645 NS_WARNING(
9646 "HTMLEditor::CreateAndInsertElement(WithTransaction::Yes) failed");
9647 return createNewElementResult;
9649 MOZ_ASSERT_IF(wasCaretPositionSuggestedAtSplit,
9650 createNewElementResult.inspect().HasCaretPointSuggestion());
9651 MOZ_ASSERT(createNewElementResult.inspect().GetNewNode());
9653 // If the new block element was moved to different element or removed by
9654 // the web app via mutation event listener, we should stop handling this
9655 // action since we cannot handle each of a lot of edge cases.
9656 if (NS_WARN_IF(
9657 createNewElementResult.inspect().GetNewNode()->GetParentNode() !=
9658 splitPoint.GetContainer())) {
9659 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
9662 return createNewElementResult;
9665 nsresult HTMLEditor::JoinNearestEditableNodesWithTransaction(
9666 nsIContent& aNodeLeft, nsIContent& aNodeRight,
9667 EditorDOMPoint* aNewFirstChildOfRightNode) {
9668 MOZ_ASSERT(IsEditActionDataAvailable());
9669 MOZ_ASSERT(aNewFirstChildOfRightNode);
9671 // Caller responsible for left and right node being the same type
9672 if (NS_WARN_IF(!aNodeLeft.GetParentNode())) {
9673 return NS_ERROR_FAILURE;
9675 // If they don't have the same parent, first move the right node to after
9676 // the left one
9677 if (aNodeLeft.GetParentNode() != aNodeRight.GetParentNode()) {
9678 Result<MoveNodeResult, nsresult> moveNodeResult =
9679 MoveNodeWithTransaction(aNodeRight, EditorDOMPoint(&aNodeLeft));
9680 if (MOZ_UNLIKELY(moveNodeResult.isErr())) {
9681 NS_WARNING("HTMLEditor::MoveNodeWithTransaction() failed");
9682 return moveNodeResult.unwrapErr();
9684 nsresult rv = moveNodeResult.inspect().SuggestCaretPointTo(
9685 *this, {SuggestCaret::OnlyIfHasSuggestion,
9686 SuggestCaret::OnlyIfTransactionsAllowedToDoIt,
9687 SuggestCaret::AndIgnoreTrivialError});
9688 if (NS_FAILED(rv)) {
9689 NS_WARNING("MoveNodeResult::SuggestCaretPointTo() failed");
9690 return rv;
9692 NS_WARNING_ASSERTION(
9693 rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
9694 "MoveNodeResult::SuggestCaretPointTo() failed, but ignored");
9697 // Separate join rules for differing blocks
9698 if (HTMLEditUtils::IsAnyListElement(&aNodeLeft) || aNodeLeft.IsText()) {
9699 // For lists, merge shallow (wouldn't want to combine list items)
9700 Result<JoinNodesResult, nsresult> joinNodesResult =
9701 JoinNodesWithTransaction(aNodeLeft, aNodeRight);
9702 if (MOZ_UNLIKELY(joinNodesResult.isErr())) {
9703 NS_WARNING("HTMLEditor::JoinNodesWithTransaction failed");
9704 return joinNodesResult.unwrapErr();
9706 *aNewFirstChildOfRightNode =
9707 joinNodesResult.inspect().AtJoinedPoint<EditorDOMPoint>();
9708 return NS_OK;
9711 // Remember the last left child, and first right child
9712 nsCOMPtr<nsIContent> lastEditableChildOfLeftContent =
9713 HTMLEditUtils::GetLastChild(aNodeLeft,
9714 {WalkTreeOption::IgnoreNonEditableNode});
9715 if (MOZ_UNLIKELY(NS_WARN_IF(!lastEditableChildOfLeftContent))) {
9716 return NS_ERROR_FAILURE;
9719 nsCOMPtr<nsIContent> firstEditableChildOfRightContent =
9720 HTMLEditUtils::GetFirstChild(aNodeRight,
9721 {WalkTreeOption::IgnoreNonEditableNode});
9722 if (NS_WARN_IF(!firstEditableChildOfRightContent)) {
9723 return NS_ERROR_FAILURE;
9726 // For list items, divs, etc., merge smart
9727 Result<JoinNodesResult, nsresult> joinNodesResult =
9728 JoinNodesWithTransaction(aNodeLeft, aNodeRight);
9729 if (MOZ_UNLIKELY(joinNodesResult.isErr())) {
9730 NS_WARNING("HTMLEditor::JoinNodesWithTransaction() failed");
9731 return joinNodesResult.unwrapErr();
9734 if ((lastEditableChildOfLeftContent->IsText() ||
9735 lastEditableChildOfLeftContent->IsElement()) &&
9736 HTMLEditUtils::CanContentsBeJoined(*lastEditableChildOfLeftContent,
9737 *firstEditableChildOfRightContent)) {
9738 nsresult rv = JoinNearestEditableNodesWithTransaction(
9739 *lastEditableChildOfLeftContent, *firstEditableChildOfRightContent,
9740 aNewFirstChildOfRightNode);
9741 NS_WARNING_ASSERTION(
9742 NS_SUCCEEDED(rv),
9743 "HTMLEditor::JoinNearestEditableNodesWithTransaction() failed");
9744 return rv;
9746 *aNewFirstChildOfRightNode =
9747 joinNodesResult.inspect().AtJoinedPoint<EditorDOMPoint>();
9748 return NS_OK;
9751 Element* HTMLEditor::GetMostDistantAncestorMailCiteElement(
9752 const nsINode& aNode) const {
9753 Element* mailCiteElement = nullptr;
9754 const bool isPlaintextEditor = IsPlaintextMailComposer();
9755 for (Element* element : aNode.InclusiveAncestorsOfType<Element>()) {
9756 if ((isPlaintextEditor && element->IsHTMLElement(nsGkAtoms::pre)) ||
9757 HTMLEditUtils::IsMailCite(*element)) {
9758 mailCiteElement = element;
9759 continue;
9761 if (element->IsHTMLElement(nsGkAtoms::body)) {
9762 break;
9765 return mailCiteElement;
9768 nsresult HTMLEditor::CacheInlineStyles(Element& Element) {
9769 MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable());
9771 nsresult rv = GetInlineStyles(
9772 Element, *TopLevelEditSubActionDataRef().mCachedPendingStyles);
9773 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
9774 "HTMLEditor::GetInlineStyles() failed");
9775 return rv;
9778 nsresult HTMLEditor::GetInlineStyles(
9779 Element& aElement, AutoPendingStyleCacheArray& aPendingStyleCacheArray) {
9780 MOZ_ASSERT(IsEditActionDataAvailable());
9781 MOZ_ASSERT(aPendingStyleCacheArray.IsEmpty());
9783 if (!IsCSSEnabled()) {
9784 // In the HTML styling mode, we should preserve the order of inline styles
9785 // specified with HTML elements, then, we can keep same order as original
9786 // one when we create new elements to apply the styles at new place.
9787 // XXX Currently, we don't preserve all inline parents, therefore, we cannot
9788 // restore all inline elements as-is. Perhaps, we should store all
9789 // inline elements with more details (e.g., all attributes), and store
9790 // same elements. For example, web apps may give style as:
9791 // em {
9792 // font-style: italic;
9793 // }
9794 // em em {
9795 // font-style: normal;
9796 // font-weight: bold;
9797 // }
9798 // but we cannot restore the style as-is.
9799 nsString value;
9800 const bool givenElementIsEditable =
9801 HTMLEditUtils::IsSimplyEditableNode(aElement);
9802 auto NeedToAppend = [&](nsStaticAtom& aTagName, nsStaticAtom* aAttribute) {
9803 if (mPendingStylesToApplyToNewContent->GetStyleState(
9804 aTagName, aAttribute) != PendingStyleState::NotUpdated) {
9805 return false; // The style has already been changed.
9807 if (aPendingStyleCacheArray.Contains(aTagName, aAttribute)) {
9808 return false; // Already preserved
9810 return true;
9812 for (Element* const inclusiveAncestor :
9813 aElement.InclusiveAncestorsOfType<Element>()) {
9814 if (HTMLEditUtils::IsBlockElement(
9815 *inclusiveAncestor,
9816 BlockInlineCheck::UseComputedDisplayOutsideStyle) ||
9817 (givenElementIsEditable &&
9818 !HTMLEditUtils::IsSimplyEditableNode(*inclusiveAncestor))) {
9819 break;
9821 if (inclusiveAncestor->IsAnyOfHTMLElements(
9822 nsGkAtoms::b, nsGkAtoms::i, nsGkAtoms::u, nsGkAtoms::s,
9823 nsGkAtoms::strike, nsGkAtoms::tt, nsGkAtoms::em,
9824 nsGkAtoms::strong, nsGkAtoms::dfn, nsGkAtoms::code,
9825 nsGkAtoms::samp, nsGkAtoms::var, nsGkAtoms::cite, nsGkAtoms::abbr,
9826 nsGkAtoms::acronym, nsGkAtoms::sub, nsGkAtoms::sup)) {
9827 nsStaticAtom& tagName = const_cast<nsStaticAtom&>(
9828 *inclusiveAncestor->NodeInfo()->NameAtom()->AsStatic());
9829 if (NeedToAppend(tagName, nullptr)) {
9830 aPendingStyleCacheArray.AppendElement(
9831 PendingStyleCache(tagName, nullptr, EmptyString()));
9833 continue;
9835 if (inclusiveAncestor->IsHTMLElement(nsGkAtoms::font)) {
9836 if (NeedToAppend(*nsGkAtoms::font, nsGkAtoms::face)) {
9837 inclusiveAncestor->GetAttr(nsGkAtoms::face, value);
9838 if (!value.IsEmpty()) {
9839 aPendingStyleCacheArray.AppendElement(
9840 PendingStyleCache(*nsGkAtoms::font, nsGkAtoms::face, value));
9841 value.Truncate();
9844 if (NeedToAppend(*nsGkAtoms::font, nsGkAtoms::size)) {
9845 inclusiveAncestor->GetAttr(nsGkAtoms::size, value);
9846 if (!value.IsEmpty()) {
9847 aPendingStyleCacheArray.AppendElement(
9848 PendingStyleCache(*nsGkAtoms::font, nsGkAtoms::size, value));
9849 value.Truncate();
9852 if (NeedToAppend(*nsGkAtoms::font, nsGkAtoms::color)) {
9853 inclusiveAncestor->GetAttr(nsGkAtoms::color, value);
9854 if (!value.IsEmpty()) {
9855 aPendingStyleCacheArray.AppendElement(
9856 PendingStyleCache(*nsGkAtoms::font, nsGkAtoms::color, value));
9857 value.Truncate();
9860 continue;
9863 return NS_OK;
9866 for (nsStaticAtom* property : {nsGkAtoms::b,
9867 nsGkAtoms::i,
9868 nsGkAtoms::u,
9869 nsGkAtoms::s,
9870 nsGkAtoms::strike,
9871 nsGkAtoms::face,
9872 nsGkAtoms::size,
9873 nsGkAtoms::color,
9874 nsGkAtoms::tt,
9875 nsGkAtoms::em,
9876 nsGkAtoms::strong,
9877 nsGkAtoms::dfn,
9878 nsGkAtoms::code,
9879 nsGkAtoms::samp,
9880 nsGkAtoms::var,
9881 nsGkAtoms::cite,
9882 nsGkAtoms::abbr,
9883 nsGkAtoms::acronym,
9884 nsGkAtoms::backgroundColor,
9885 nsGkAtoms::sub,
9886 nsGkAtoms::sup}) {
9887 const EditorInlineStyle style =
9888 property == nsGkAtoms::face || property == nsGkAtoms::size ||
9889 property == nsGkAtoms::color
9890 ? EditorInlineStyle(*nsGkAtoms::font, property)
9891 : EditorInlineStyle(*property);
9892 // If type-in state is set, don't intervene
9893 const PendingStyleState styleState =
9894 mPendingStylesToApplyToNewContent->GetStyleState(*style.mHTMLProperty,
9895 style.mAttribute);
9896 if (styleState != PendingStyleState::NotUpdated) {
9897 continue;
9899 bool isSet = false;
9900 nsString value; // Don't use nsAutoString here because it requires memcpy
9901 // at creating new PendingStyleCache instance.
9902 // Don't use CSS for <font size>, we don't support it usefully (bug 780035)
9903 if (property == nsGkAtoms::size) {
9904 isSet = HTMLEditUtils::IsInlineStyleSetByElement(aElement, style, nullptr,
9905 &value);
9906 } else if (style.IsCSSSettable(aElement)) {
9907 Result<bool, nsresult> isComputedCSSEquivalentToStyleOrError =
9908 CSSEditUtils::IsComputedCSSEquivalentTo(*this, aElement, style,
9909 value);
9910 if (MOZ_UNLIKELY(isComputedCSSEquivalentToStyleOrError.isErr())) {
9911 NS_WARNING("CSSEditUtils::IsComputedCSSEquivalentTo() failed");
9912 return isComputedCSSEquivalentToStyleOrError.unwrapErr();
9914 isSet = isComputedCSSEquivalentToStyleOrError.unwrap();
9916 if (isSet) {
9917 aPendingStyleCacheArray.AppendElement(
9918 style.ToPendingStyleCache(std::move(value)));
9921 return NS_OK;
9924 nsresult HTMLEditor::ReapplyCachedStyles() {
9925 MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable());
9927 // The idea here is to examine our cached list of styles and see if any have
9928 // been removed. If so, add typeinstate for them, so that they will be
9929 // reinserted when new content is added.
9931 if (TopLevelEditSubActionDataRef().mCachedPendingStyles->IsEmpty() ||
9932 !SelectionRef().RangeCount()) {
9933 return NS_OK;
9936 // remember if we are in css mode
9937 const bool useCSS = IsCSSEnabled();
9939 const RangeBoundary& atStartOfSelection =
9940 SelectionRef().GetRangeAt(0)->StartRef();
9941 const RefPtr<Element> startContainerElement =
9942 atStartOfSelection.Container() &&
9943 atStartOfSelection.Container()->IsContent()
9944 ? atStartOfSelection.Container()->GetAsElementOrParentElement()
9945 : nullptr;
9946 if (NS_WARN_IF(!startContainerElement)) {
9947 return NS_OK;
9950 AutoPendingStyleCacheArray styleCacheArrayAtInsertionPoint;
9951 nsresult rv =
9952 GetInlineStyles(*startContainerElement, styleCacheArrayAtInsertionPoint);
9953 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
9954 return NS_ERROR_EDITOR_DESTROYED;
9956 if (NS_FAILED(rv)) {
9957 NS_WARNING("HTMLEditor::GetInlineStyles() failed, but ignored");
9958 return NS_OK;
9961 for (PendingStyleCache& styleCacheBeforeEdit :
9962 Reversed(*TopLevelEditSubActionDataRef().mCachedPendingStyles)) {
9963 bool isFirst = false, isAny = false, isAll = false;
9964 nsAutoString currentValue;
9965 const EditorInlineStyle inlineStyle = styleCacheBeforeEdit.ToInlineStyle();
9966 if (useCSS && inlineStyle.IsCSSSettable(*startContainerElement)) {
9967 // check computed style first in css case
9968 // MOZ_KnownLive(styleCacheBeforeEdit.*) because they are nsStaticAtom
9969 // and its instances are alive until shutting down.
9970 Result<bool, nsresult> isComputedCSSEquivalentToStyleOrError =
9971 CSSEditUtils::IsComputedCSSEquivalentTo(*this, *startContainerElement,
9972 inlineStyle, currentValue);
9973 if (MOZ_UNLIKELY(isComputedCSSEquivalentToStyleOrError.isErr())) {
9974 NS_WARNING("CSSEditUtils::IsComputedCSSEquivalentTo() failed");
9975 return isComputedCSSEquivalentToStyleOrError.unwrapErr();
9977 isAny = isComputedCSSEquivalentToStyleOrError.unwrap();
9979 if (!isAny) {
9980 // then check typeinstate and html style
9981 nsresult rv = GetInlinePropertyBase(
9982 inlineStyle, &styleCacheBeforeEdit.AttributeValueOrCSSValueRef(),
9983 &isFirst, &isAny, &isAll, &currentValue);
9984 if (NS_FAILED(rv)) {
9985 NS_WARNING("HTMLEditor::GetInlinePropertyBase() failed");
9986 return rv;
9989 // This style has disappeared through deletion. Let's add the styles to
9990 // mPendingStylesToApplyToNewContent when same style isn't applied to the
9991 // node already.
9992 if (isAny &&
9993 !IsPendingStyleCachePreservingSubAction(GetTopLevelEditSubAction())) {
9994 continue;
9996 AutoPendingStyleCacheArray::index_type index =
9997 styleCacheArrayAtInsertionPoint.IndexOf(
9998 styleCacheBeforeEdit.TagRef(), styleCacheBeforeEdit.GetAttribute());
9999 if (index == AutoPendingStyleCacheArray::NoIndex ||
10000 styleCacheBeforeEdit.AttributeValueOrCSSValueRef() !=
10001 styleCacheArrayAtInsertionPoint.ElementAt(index)
10002 .AttributeValueOrCSSValueRef()) {
10003 mPendingStylesToApplyToNewContent->PreserveStyle(styleCacheBeforeEdit);
10006 return NS_OK;
10009 nsresult HTMLEditor::InsertBRElementToEmptyListItemsAndTableCellsInRange(
10010 const RawRangeBoundary& aStartRef, const RawRangeBoundary& aEndRef) {
10011 MOZ_ASSERT(IsEditActionDataAvailable());
10013 AutoTArray<OwningNonNull<Element>, 64> arrayOfEmptyElements;
10014 DOMIterator iter;
10015 if (NS_FAILED(iter.Init(aStartRef, aEndRef))) {
10016 NS_WARNING("DOMIterator::Init() failed");
10017 return NS_ERROR_FAILURE;
10019 iter.AppendNodesToArray(
10020 +[](nsINode& aNode, void* aSelf) {
10021 MOZ_ASSERT(Element::FromNode(&aNode));
10022 MOZ_ASSERT(aSelf);
10023 Element* element = aNode.AsElement();
10024 if (!EditorUtils::IsEditableContent(*element, EditorType::HTML) ||
10025 (!HTMLEditUtils::IsListItem(element) &&
10026 !HTMLEditUtils::IsTableCellOrCaption(*element))) {
10027 return false;
10029 return HTMLEditUtils::IsEmptyNode(
10030 *element, {EmptyCheckOption::TreatSingleBRElementAsVisible,
10031 EmptyCheckOption::TreatNonEditableContentAsInvisible});
10033 arrayOfEmptyElements, this);
10035 // Put padding <br> elements for empty <li> and <td>.
10036 EditorDOMPoint pointToPutCaret;
10037 for (auto& emptyElement : arrayOfEmptyElements) {
10038 // Need to put br at END of node. It may have empty containers in it and
10039 // still pass the "IsEmptyNode" test, and we want the br's to be after
10040 // them. Also, we want the br to be after the selection if the selection
10041 // is in this node.
10042 EditorDOMPoint endOfNode(EditorDOMPoint::AtEndOf(emptyElement));
10043 Result<CreateElementResult, nsresult> insertPaddingBRElementResult =
10044 InsertPaddingBRElementForEmptyLastLineWithTransaction(endOfNode);
10045 if (MOZ_UNLIKELY(insertPaddingBRElementResult.isErr())) {
10046 NS_WARNING(
10047 "HTMLEditor::InsertPaddingBRElementForEmptyLastLineWithTransaction() "
10048 "failed");
10049 return insertPaddingBRElementResult.unwrapErr();
10051 CreateElementResult unwrappedInsertPaddingBRElementResult =
10052 insertPaddingBRElementResult.unwrap();
10053 unwrappedInsertPaddingBRElementResult.MoveCaretPointTo(
10054 pointToPutCaret, *this,
10055 {SuggestCaret::OnlyIfHasSuggestion,
10056 SuggestCaret::OnlyIfTransactionsAllowedToDoIt});
10058 if (pointToPutCaret.IsSet()) {
10059 nsresult rv = CollapseSelectionTo(pointToPutCaret);
10060 if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) {
10061 NS_WARNING(
10062 "EditorBase::CollapseSelectionTo() caused destroying the editor");
10063 return NS_ERROR_EDITOR_DESTROYED;
10065 NS_WARNING_ASSERTION(
10066 NS_SUCCEEDED(rv),
10067 "EditorBase::CollapseSelectionTo() failed, but ignored");
10069 return NS_OK;
10072 void HTMLEditor::SetSelectionInterlinePosition() {
10073 MOZ_ASSERT(IsEditActionDataAvailable());
10074 MOZ_ASSERT(SelectionRef().IsCollapsed());
10076 // Get the (collapsed) selection location
10077 const nsRange* firstRange = SelectionRef().GetRangeAt(0);
10078 if (NS_WARN_IF(!firstRange)) {
10079 return;
10082 EditorDOMPoint atCaret(firstRange->StartRef());
10083 if (NS_WARN_IF(!atCaret.IsSet())) {
10084 return;
10086 MOZ_ASSERT(atCaret.IsSetAndValid());
10088 // First, let's check to see if we are after a `<br>`. We take care of this
10089 // special-case first so that we don't accidentally fall through into one of
10090 // the other conditionals.
10091 // XXX Although I don't understand "interline position", if caret is
10092 // immediately after non-editable contents, but previous editable
10093 // content is `<br>`, does this do right thing?
10094 if (Element* editingHost = ComputeEditingHost()) {
10095 if (nsIContent* previousEditableContentInBlock =
10096 HTMLEditUtils::GetPreviousContent(
10097 atCaret,
10098 {WalkTreeOption::IgnoreNonEditableNode,
10099 WalkTreeOption::StopAtBlockBoundary},
10100 BlockInlineCheck::UseComputedDisplayStyle, editingHost)) {
10101 if (previousEditableContentInBlock->IsHTMLElement(nsGkAtoms::br)) {
10102 DebugOnly<nsresult> rvIgnored = SelectionRef().SetInterlinePosition(
10103 InterlinePosition::StartOfNextLine);
10104 NS_WARNING_ASSERTION(
10105 NS_SUCCEEDED(rvIgnored),
10106 "Selection::SetInterlinePosition(InterlinePosition::"
10107 "StartOfNextLine) failed, but ignored");
10108 return;
10113 if (!atCaret.GetChild()) {
10114 return;
10117 // If caret is immediately after a block, set interline position to "right".
10118 // XXX Although I don't understand "interline position", if caret is
10119 // immediately after non-editable contents, but previous editable
10120 // content is a block, does this do right thing?
10121 if (nsIContent* previousEditableContentInBlockAtCaret =
10122 HTMLEditUtils::GetPreviousSibling(
10123 *atCaret.GetChild(), {WalkTreeOption::IgnoreNonEditableNode})) {
10124 if (HTMLEditUtils::IsBlockElement(
10125 *previousEditableContentInBlockAtCaret,
10126 BlockInlineCheck::UseComputedDisplayStyle)) {
10127 DebugOnly<nsresult> rvIgnored = SelectionRef().SetInterlinePosition(
10128 InterlinePosition::StartOfNextLine);
10129 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
10130 "Selection::SetInterlinePosition(InterlinePosition::"
10131 "StartOfNextLine) failed, but ignored");
10132 return;
10136 // If caret is immediately before a block, set interline position to "left".
10137 // XXX Although I don't understand "interline position", if caret is
10138 // immediately before non-editable contents, but next editable
10139 // content is a block, does this do right thing?
10140 if (nsIContent* nextEditableContentInBlockAtCaret =
10141 HTMLEditUtils::GetNextSibling(
10142 *atCaret.GetChild(), {WalkTreeOption::IgnoreNonEditableNode})) {
10143 if (HTMLEditUtils::IsBlockElement(
10144 *nextEditableContentInBlockAtCaret,
10145 BlockInlineCheck::UseComputedDisplayStyle)) {
10146 DebugOnly<nsresult> rvIgnored =
10147 SelectionRef().SetInterlinePosition(InterlinePosition::EndOfLine);
10148 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
10149 "Selection::SetInterlinePosition(InterlinePosition::"
10150 "EndOfLine) failed, but ignored");
10155 nsresult HTMLEditor::AdjustCaretPositionAndEnsurePaddingBRElement(
10156 nsIEditor::EDirection aDirectionAndAmount) {
10157 MOZ_ASSERT(IsEditActionDataAvailable());
10158 MOZ_ASSERT(SelectionRef().IsCollapsed());
10160 auto point = GetFirstSelectionStartPoint<EditorDOMPoint>();
10161 if (NS_WARN_IF(!point.IsInContentNode())) {
10162 return NS_ERROR_FAILURE;
10165 // If selection start is not editable, climb up the tree until editable one.
10166 while (!EditorUtils::IsEditableContent(*point.ContainerAs<nsIContent>(),
10167 EditorType::HTML)) {
10168 point.Set(point.GetContainer());
10169 if (NS_WARN_IF(!point.IsInContentNode())) {
10170 return NS_ERROR_FAILURE;
10174 // If caret is in empty block element, we need to insert a `<br>` element
10175 // because the block should have one-line height.
10176 // XXX Even if only a part of the block is editable, shouldn't we put
10177 // caret if the block element is now empty?
10178 if (Element* const editableBlockElement =
10179 HTMLEditUtils::GetInclusiveAncestorElement(
10180 *point.ContainerAs<nsIContent>(),
10181 HTMLEditUtils::ClosestEditableBlockElement,
10182 BlockInlineCheck::UseComputedDisplayStyle)) {
10183 if (editableBlockElement &&
10184 HTMLEditUtils::IsEmptyNode(
10185 *editableBlockElement,
10186 {EmptyCheckOption::TreatSingleBRElementAsVisible}) &&
10187 HTMLEditUtils::CanNodeContain(*point.GetContainer(), *nsGkAtoms::br)) {
10188 Element* bodyOrDocumentElement = GetRoot();
10189 if (NS_WARN_IF(!bodyOrDocumentElement)) {
10190 return NS_ERROR_FAILURE;
10192 if (point.GetContainer() == bodyOrDocumentElement) {
10193 // Our root node is completely empty. Don't add a <br> here.
10194 // AfterEditInner() will add one for us when it calls
10195 // EditorBase::MaybeCreatePaddingBRElementForEmptyEditor().
10196 // XXX This kind of dependency between methods makes us spaghetti.
10197 // Let's handle it here later.
10198 // XXX This looks odd check. If active editing host is not a
10199 // `<body>`, what are we doing?
10200 return NS_OK;
10202 Result<CreateElementResult, nsresult> insertPaddingBRElementResult =
10203 InsertPaddingBRElementForEmptyLastLineWithTransaction(point);
10204 if (MOZ_UNLIKELY(insertPaddingBRElementResult.isErr())) {
10205 NS_WARNING(
10206 "HTMLEditor::InsertPaddingBRElementForEmptyLastLineWithTransaction("
10207 ") failed");
10208 return insertPaddingBRElementResult.unwrapErr();
10210 nsresult rv = insertPaddingBRElementResult.inspect().SuggestCaretPointTo(
10211 *this, {SuggestCaret::OnlyIfHasSuggestion,
10212 SuggestCaret::OnlyIfTransactionsAllowedToDoIt,
10213 SuggestCaret::AndIgnoreTrivialError});
10214 if (NS_FAILED(rv)) {
10215 NS_WARNING("CreateElementResult::SuggestCaretPointTo() failed");
10216 return rv;
10218 NS_WARNING_ASSERTION(
10219 rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
10220 "CreateElementResult::SuggestCaretPointTo() failed, but ignored");
10221 return NS_OK;
10225 // XXX Perhaps, we should do something if we're in a data node but not
10226 // a text node.
10227 if (point.IsInTextNode()) {
10228 return NS_OK;
10231 // Do we need to insert a padding <br> element for empty last line? We do
10232 // if we are:
10233 // 1) prior node is in same block where selection is AND
10234 // 2) prior node is a br AND
10235 // 3) that br is not visible
10236 RefPtr<Element> editingHost = ComputeEditingHost();
10237 if (!editingHost) {
10238 return NS_OK;
10241 if (nsCOMPtr<nsIContent> previousEditableContent =
10242 HTMLEditUtils::GetPreviousContent(
10243 point, {WalkTreeOption::IgnoreNonEditableNode},
10244 BlockInlineCheck::UseComputedDisplayStyle, editingHost)) {
10245 // If caret and previous editable content are in same block element
10246 // (even if it's a non-editable element), we should put a padding <br>
10247 // element at end of the block.
10248 const Element* const blockElementContainingCaret =
10249 HTMLEditUtils::GetInclusiveAncestorElement(
10250 *point.ContainerAs<nsIContent>(),
10251 HTMLEditUtils::ClosestBlockElement,
10252 BlockInlineCheck::UseComputedDisplayStyle);
10253 const Element* const blockElementContainingPreviousEditableContent =
10254 HTMLEditUtils::GetAncestorElement(
10255 *previousEditableContent, HTMLEditUtils::ClosestBlockElement,
10256 BlockInlineCheck::UseComputedDisplayStyle);
10257 // If previous editable content of caret is in same block and a `<br>`
10258 // element, we need to adjust interline position.
10259 if (blockElementContainingCaret &&
10260 blockElementContainingCaret ==
10261 blockElementContainingPreviousEditableContent &&
10262 point.ContainerAs<nsIContent>()->GetEditingHost() ==
10263 previousEditableContent->GetEditingHost() &&
10264 previousEditableContent &&
10265 previousEditableContent->IsHTMLElement(nsGkAtoms::br)) {
10266 // If it's an invisible `<br>` element, we need to insert a padding
10267 // `<br>` element for making empty line have one-line height.
10268 if (HTMLEditUtils::IsInvisibleBRElement(*previousEditableContent) &&
10269 !EditorUtils::IsPaddingBRElementForEmptyLastLine(
10270 *previousEditableContent)) {
10271 AutoEditorDOMPointChildInvalidator lockOffset(point);
10272 Result<CreateElementResult, nsresult> insertPaddingBRElementResult =
10273 InsertPaddingBRElementForEmptyLastLineWithTransaction(point);
10274 if (MOZ_UNLIKELY(insertPaddingBRElementResult.isErr())) {
10275 NS_WARNING(
10276 "HTMLEditor::"
10277 "InsertPaddingBRElementForEmptyLastLineWithTransaction() failed");
10278 return insertPaddingBRElementResult.unwrapErr();
10280 insertPaddingBRElementResult.inspect().IgnoreCaretPointSuggestion();
10281 nsresult rv = CollapseSelectionTo(EditorRawDOMPoint(
10282 insertPaddingBRElementResult.inspect().GetNewNode(),
10283 InterlinePosition::StartOfNextLine));
10284 if (NS_FAILED(rv)) {
10285 NS_WARNING("EditorBase::CollapseSelectionTo() failed");
10286 return rv;
10289 // If it's a visible `<br>` element and next editable content is a
10290 // padding `<br>` element, we need to set interline position.
10291 else if (nsIContent* nextEditableContentInBlock =
10292 HTMLEditUtils::GetNextContent(
10293 *previousEditableContent,
10294 {WalkTreeOption::IgnoreNonEditableNode,
10295 WalkTreeOption::StopAtBlockBoundary},
10296 BlockInlineCheck::UseComputedDisplayStyle,
10297 editingHost)) {
10298 if (EditorUtils::IsPaddingBRElementForEmptyLastLine(
10299 *nextEditableContentInBlock)) {
10300 // Make it stick to the padding `<br>` element so that it will be
10301 // on blank line.
10302 DebugOnly<nsresult> rvIgnored = SelectionRef().SetInterlinePosition(
10303 InterlinePosition::StartOfNextLine);
10304 NS_WARNING_ASSERTION(
10305 NS_SUCCEEDED(rvIgnored),
10306 "Selection::SetInterlinePosition(InterlinePosition::"
10307 "StartOfNextLine) failed, but ignored");
10313 // If previous editable content in same block is `<br>`, text node, `<img>`
10314 // or `<hr>`, current caret position is fine.
10315 if (nsIContent* previousEditableContentInBlock =
10316 HTMLEditUtils::GetPreviousContent(
10317 point,
10318 {WalkTreeOption::IgnoreNonEditableNode,
10319 WalkTreeOption::StopAtBlockBoundary},
10320 BlockInlineCheck::UseComputedDisplayStyle, editingHost)) {
10321 if (previousEditableContentInBlock->IsHTMLElement(nsGkAtoms::br) ||
10322 previousEditableContentInBlock->IsText() ||
10323 HTMLEditUtils::IsImage(previousEditableContentInBlock) ||
10324 previousEditableContentInBlock->IsHTMLElement(nsGkAtoms::hr)) {
10325 return NS_OK;
10329 // If next editable content in same block is `<br>`, text node, `<img>` or
10330 // `<hr>`, current caret position is fine.
10331 if (nsIContent* nextEditableContentInBlock = HTMLEditUtils::GetNextContent(
10332 point,
10333 {WalkTreeOption::IgnoreNonEditableNode,
10334 WalkTreeOption::StopAtBlockBoundary},
10335 BlockInlineCheck::UseComputedDisplayStyle, editingHost)) {
10336 if (nextEditableContentInBlock->IsText() ||
10337 nextEditableContentInBlock->IsAnyOfHTMLElements(
10338 nsGkAtoms::br, nsGkAtoms::img, nsGkAtoms::hr)) {
10339 return NS_OK;
10343 // Otherwise, look for a near editable content towards edit action direction.
10345 // If there is no editable content, keep current caret position.
10346 // XXX Why do we treat `nsIEditor::ePreviousWord` etc as forward direction?
10347 nsIContent* nearEditableContent = HTMLEditUtils::GetAdjacentContentToPutCaret(
10348 point,
10349 aDirectionAndAmount == nsIEditor::ePrevious ? WalkTreeDirection::Backward
10350 : WalkTreeDirection::Forward,
10351 *editingHost);
10352 if (!nearEditableContent) {
10353 return NS_OK;
10356 EditorRawDOMPoint pointToPutCaret =
10357 HTMLEditUtils::GetGoodCaretPointFor<EditorRawDOMPoint>(
10358 *nearEditableContent, aDirectionAndAmount);
10359 if (!pointToPutCaret.IsSet()) {
10360 NS_WARNING("HTMLEditUtils::GetGoodCaretPointFor() failed");
10361 return NS_ERROR_FAILURE;
10363 nsresult rv = CollapseSelectionTo(pointToPutCaret);
10364 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
10365 "EditorBase::CollapseSelectionTo() failed");
10366 return rv;
10369 nsresult HTMLEditor::RemoveEmptyNodesIn(const EditorDOMRange& aRange) {
10370 MOZ_ASSERT(IsEditActionDataAvailable());
10371 MOZ_ASSERT(aRange.IsPositioned());
10373 // Some general notes on the algorithm used here: the goal is to examine all
10374 // the nodes in aRange, and remove the empty ones. We do this by
10375 // using a content iterator to traverse all the nodes in the range, and
10376 // placing the empty nodes into an array. After finishing the iteration,
10377 // we delete the empty nodes in the array. (They cannot be deleted as we
10378 // find them because that would invalidate the iterator.)
10380 // Since checking to see if a node is empty can be costly for nodes with
10381 // many descendants, there are some optimizations made. I rely on the fact
10382 // that the iterator is post-order: it will visit children of a node before
10383 // visiting the parent node. So if I find that a child node is not empty, I
10384 // know that its parent is not empty without even checking. So I put the
10385 // parent on a "skipList" which is just a voidArray of nodes I can skip the
10386 // empty check on. If I encounter a node on the skiplist, i skip the
10387 // processing for that node and replace its slot in the skiplist with that
10388 // node's parent.
10390 // An interesting idea is to go ahead and regard parent nodes that are NOT
10391 // on the skiplist as being empty (without even doing the IsEmptyNode check)
10392 // on the theory that if they weren't empty, we would have encountered a
10393 // non-empty child earlier and thus put this parent node on the skiplist.
10395 // Unfortunately I can't use that strategy here, because the range may
10396 // include some children of a node while excluding others. Thus I could
10397 // find all the _examined_ children empty, but still not have an empty
10398 // parent.
10400 const RawRangeBoundary endOfRange = [&]() {
10401 // If the range is not collapsed and end of the range is start of a
10402 // container, it means that the inclusive ancestor empty element may be
10403 // created by splitting the left nodes.
10404 if (aRange.Collapsed() || !aRange.IsInContentNodes() ||
10405 !aRange.EndRef().IsStartOfContainer()) {
10406 return aRange.EndRef().ToRawRangeBoundary();
10408 nsINode* const commonAncestor =
10409 nsContentUtils::GetClosestCommonInclusiveAncestor(
10410 aRange.StartRef().ContainerAs<nsIContent>(),
10411 aRange.EndRef().ContainerAs<nsIContent>());
10412 if (!commonAncestor) {
10413 return aRange.EndRef().ToRawRangeBoundary();
10415 nsIContent* maybeRightContent = nullptr;
10416 for (nsIContent* content : aRange.EndRef()
10417 .ContainerAs<nsIContent>()
10418 ->InclusiveAncestorsOfType<nsIContent>()) {
10419 if (!HTMLEditUtils::IsSimplyEditableNode(*content) ||
10420 content == commonAncestor) {
10421 break;
10423 if (aRange.StartRef().ContainerAs<nsIContent>() == content) {
10424 break;
10426 EmptyCheckOptions options = {
10427 EmptyCheckOption::TreatListItemAsVisible,
10428 EmptyCheckOption::TreatTableCellAsVisible,
10429 EmptyCheckOption::TreatNonEditableContentAsInvisible};
10430 if (!HTMLEditUtils::IsBlockElement(
10431 *content, BlockInlineCheck::UseComputedDisplayStyle)) {
10432 options += EmptyCheckOption::TreatSingleBRElementAsVisible;
10434 if (!HTMLEditUtils::IsEmptyNode(*content, options)) {
10435 break;
10437 maybeRightContent = content;
10439 if (!maybeRightContent) {
10440 return aRange.EndRef().ToRawRangeBoundary();
10442 return EditorRawDOMPoint::After(*maybeRightContent).ToRawRangeBoundary();
10443 }();
10445 PostContentIterator postOrderIter;
10446 nsresult rv =
10447 postOrderIter.Init(aRange.StartRef().ToRawRangeBoundary(), endOfRange);
10448 if (NS_FAILED(rv)) {
10449 NS_WARNING("PostContentIterator::Init() failed");
10450 return rv;
10453 AutoTArray<OwningNonNull<nsIContent>, 64> arrayOfEmptyContents,
10454 arrayOfEmptyCites;
10456 // Collect empty nodes first.
10458 const bool isMailEditor = IsMailEditor();
10459 AutoTArray<OwningNonNull<nsIContent>, 64> knownNonEmptyContents;
10460 Maybe<AutoRangeArray> maybeSelectionRanges;
10461 for (; !postOrderIter.IsDone(); postOrderIter.Next()) {
10462 MOZ_ASSERT(postOrderIter.GetCurrentNode()->IsContent());
10464 nsIContent* content = postOrderIter.GetCurrentNode()->AsContent();
10465 nsIContent* parentContent = content->GetParent();
10467 size_t idx = knownNonEmptyContents.IndexOf(content);
10468 if (idx != decltype(knownNonEmptyContents)::NoIndex) {
10469 // This node is on our skip list. Skip processing for this node, and
10470 // replace its value in the skip list with the value of its parent
10471 if (parentContent) {
10472 knownNonEmptyContents[idx] = parentContent;
10474 continue;
10477 const bool isEmptyNode = [&]() {
10478 if (!content->IsElement()) {
10479 return false;
10481 const bool isMailCite =
10482 isMailEditor && HTMLEditUtils::IsMailCite(*content->AsElement());
10483 const bool isCandidate = [&]() {
10484 if (content->IsHTMLElement(nsGkAtoms::body)) {
10485 // Don't delete the body
10486 return false;
10488 if (isMailCite || content->IsHTMLElement(nsGkAtoms::a) ||
10489 HTMLEditUtils::IsInlineStyle(content) ||
10490 HTMLEditUtils::IsAnyListElement(content) ||
10491 content->IsHTMLElement(nsGkAtoms::div)) {
10492 // Only consider certain nodes to be empty for purposes of removal
10493 return true;
10495 if (HTMLEditUtils::IsFormatElementForFormatBlockCommand(*content) ||
10496 HTMLEditUtils::IsListItem(content) ||
10497 content->IsHTMLElement(nsGkAtoms::blockquote)) {
10498 // These node types are candidates if selection is not in them. If
10499 // it is one of these, don't delete if selection inside. This is so
10500 // we can create empty headings, etc., for the user to type into.
10501 if (maybeSelectionRanges.isNothing()) {
10502 maybeSelectionRanges.emplace(SelectionRef());
10504 return !maybeSelectionRanges
10505 ->IsAtLeastOneContainerOfRangeBoundariesInclusiveDescendantOf(
10506 *content);
10508 return false;
10509 }();
10511 if (!isCandidate) {
10512 return false;
10515 // We delete mailcites even if they have a solo br in them. Other
10516 // nodes we require to be empty.
10517 HTMLEditUtils::EmptyCheckOptions options{
10518 EmptyCheckOption::TreatListItemAsVisible,
10519 EmptyCheckOption::TreatTableCellAsVisible};
10520 if (!isMailCite) {
10521 options += EmptyCheckOption::TreatSingleBRElementAsVisible;
10522 } else {
10523 // XXX Maybe unnecessary to specify this.
10524 options += EmptyCheckOption::TreatNonEditableContentAsInvisible;
10526 if (!HTMLEditUtils::IsEmptyNode(*content, options)) {
10527 return false;
10530 if (isMailCite) {
10531 // mailcites go on a separate list from other empty nodes
10532 arrayOfEmptyCites.AppendElement(*content);
10534 // Don't delete non-editable nodes in this method because this is a
10535 // clean up method to remove unnecessary nodes of the result of
10536 // editing. So, we shouldn't delete non-editable nodes which were
10537 // there before editing. Additionally, if the element is some special
10538 // elements such as <body>, we shouldn't delete it.
10539 else if (HTMLEditUtils::IsSimplyEditableNode(*content) &&
10540 HTMLEditUtils::IsRemovableNode(*content)) {
10541 arrayOfEmptyContents.AppendElement(*content);
10543 return true;
10544 }();
10545 if (!isEmptyNode && parentContent) {
10546 knownNonEmptyContents.AppendElement(*parentContent);
10548 } // end of the for-loop iterating with postOrderIter
10551 // now delete the empty nodes
10552 for (OwningNonNull<nsIContent>& emptyContent : arrayOfEmptyContents) {
10553 // MOZ_KnownLive due to bug 1622253
10554 nsresult rv = DeleteNodeWithTransaction(MOZ_KnownLive(emptyContent));
10555 if (NS_FAILED(rv)) {
10556 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
10557 return rv;
10561 // Now delete the empty mailcites. This is a separate step because we want
10562 // to pull out any br's and preserve them.
10563 EditorDOMPoint pointToPutCaret;
10564 for (OwningNonNull<nsIContent>& emptyCite : arrayOfEmptyCites) {
10565 if (!HTMLEditUtils::IsEmptyNode(
10566 emptyCite,
10567 {EmptyCheckOption::TreatSingleBRElementAsVisible,
10568 EmptyCheckOption::TreatListItemAsVisible,
10569 EmptyCheckOption::TreatTableCellAsVisible,
10570 EmptyCheckOption::TreatNonEditableContentAsInvisible})) {
10571 // We are deleting a cite that has just a `<br>`. We want to delete cite,
10572 // but preserve `<br>`.
10573 Result<CreateElementResult, nsresult> insertBRElementResult =
10574 InsertBRElement(WithTransaction::Yes, EditorDOMPoint(emptyCite));
10575 if (MOZ_UNLIKELY(insertBRElementResult.isErr())) {
10576 NS_WARNING("HTMLEditor::InsertBRElement(WithTransaction::Yes) failed");
10577 return insertBRElementResult.unwrapErr();
10579 CreateElementResult unwrappedInsertBRElementResult =
10580 insertBRElementResult.unwrap();
10581 // XXX Is this intentional selection change?
10582 unwrappedInsertBRElementResult.MoveCaretPointTo(
10583 pointToPutCaret, *this,
10584 {SuggestCaret::OnlyIfHasSuggestion,
10585 SuggestCaret::OnlyIfTransactionsAllowedToDoIt});
10586 MOZ_ASSERT(unwrappedInsertBRElementResult.GetNewNode());
10588 // MOZ_KnownLive because 'arrayOfEmptyCites' is guaranteed to keep it alive.
10589 nsresult rv = DeleteNodeWithTransaction(MOZ_KnownLive(emptyCite));
10590 if (NS_FAILED(rv)) {
10591 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
10592 return rv;
10595 // XXX Is this intentional selection change?
10596 if (pointToPutCaret.IsSet()) {
10597 nsresult rv = CollapseSelectionTo(pointToPutCaret);
10598 if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) {
10599 NS_WARNING(
10600 "EditorBase::CollapseSelectionTo() caused destroying the editor");
10601 return NS_ERROR_EDITOR_DESTROYED;
10603 NS_WARNING_ASSERTION(
10604 NS_SUCCEEDED(rv),
10605 "EditorBase::CollapseSelectionTo() failed, but ignored");
10608 return NS_OK;
10611 nsresult HTMLEditor::LiftUpListItemElement(
10612 Element& aListItemElement,
10613 LiftUpFromAllParentListElements aLiftUpFromAllParentListElements) {
10614 MOZ_ASSERT(IsEditActionDataAvailable());
10616 if (!HTMLEditUtils::IsListItem(&aListItemElement)) {
10617 return NS_ERROR_INVALID_ARG;
10620 if (NS_WARN_IF(!aListItemElement.GetParentElement()) ||
10621 NS_WARN_IF(!aListItemElement.GetParentElement()->GetParentNode())) {
10622 return NS_ERROR_FAILURE;
10625 // if it's first or last list item, don't need to split the list
10626 // otherwise we do.
10627 const bool isFirstListItem = HTMLEditUtils::IsFirstChild(
10628 aListItemElement, {WalkTreeOption::IgnoreNonEditableNode});
10629 const bool isLastListItem = HTMLEditUtils::IsLastChild(
10630 aListItemElement, {WalkTreeOption::IgnoreNonEditableNode});
10632 Element* leftListElement = aListItemElement.GetParentElement();
10633 if (NS_WARN_IF(!leftListElement)) {
10634 return NS_ERROR_FAILURE;
10637 // If it's at middle of parent list element, split the parent list element.
10638 // Then, aListItem becomes the first list item of the right list element.
10639 if (!isFirstListItem && !isLastListItem) {
10640 EditorDOMPoint atListItemElement(&aListItemElement);
10641 if (NS_WARN_IF(!atListItemElement.IsSet())) {
10642 return NS_ERROR_FAILURE;
10644 MOZ_ASSERT(atListItemElement.IsSetAndValid());
10645 Result<SplitNodeResult, nsresult> splitListItemParentResult =
10646 SplitNodeWithTransaction(atListItemElement);
10647 if (MOZ_UNLIKELY(splitListItemParentResult.isErr())) {
10648 NS_WARNING("HTMLEditor::SplitNodeWithTransaction() failed");
10649 return splitListItemParentResult.unwrapErr();
10651 nsresult rv = splitListItemParentResult.inspect().SuggestCaretPointTo(
10652 *this, {SuggestCaret::OnlyIfTransactionsAllowedToDoIt});
10653 if (NS_FAILED(rv)) {
10654 NS_WARNING("SplitNodeResult::SuggestCaretPointTo() failed");
10655 return rv;
10658 leftListElement =
10659 splitListItemParentResult.inspect().GetPreviousContentAs<Element>();
10660 if (MOZ_UNLIKELY(!leftListElement)) {
10661 NS_WARNING(
10662 "HTMLEditor::SplitNodeWithTransaction() didn't return left list "
10663 "element");
10664 return NS_ERROR_FAILURE;
10668 // In most cases, insert the list item into the new left list node..
10669 EditorDOMPoint pointToInsertListItem(leftListElement);
10670 if (NS_WARN_IF(!pointToInsertListItem.IsSet())) {
10671 return NS_ERROR_FAILURE;
10674 // But when the list item was the first child of the right list, it should
10675 // be inserted between the both list elements. This allows user to hit
10676 // Enter twice at a list item breaks the parent list node.
10677 if (!isFirstListItem) {
10678 DebugOnly<bool> advanced = pointToInsertListItem.AdvanceOffset();
10679 NS_WARNING_ASSERTION(advanced,
10680 "Failed to advance offset to right list node");
10683 EditorDOMPoint pointToPutCaret;
10685 Result<MoveNodeResult, nsresult> moveListItemElementResult =
10686 MoveNodeWithTransaction(aListItemElement, pointToInsertListItem);
10687 if (MOZ_UNLIKELY(moveListItemElementResult.isErr())) {
10688 NS_WARNING("HTMLEditor::MoveNodeWithTransaction() failed");
10689 return moveListItemElementResult.unwrapErr();
10691 MoveNodeResult unwrappedMoveListItemElementResult =
10692 moveListItemElementResult.unwrap();
10693 unwrappedMoveListItemElementResult.MoveCaretPointTo(
10694 pointToPutCaret, *this,
10695 {SuggestCaret::OnlyIfHasSuggestion,
10696 SuggestCaret::OnlyIfTransactionsAllowedToDoIt});
10699 // Unwrap list item contents if they are no longer in a list
10700 // XXX If the parent list element is a child of another list element
10701 // (although invalid tree), the list item element won't be unwrapped.
10702 // That makes the parent ancestor element tree valid, but might be
10703 // unexpected result.
10704 // XXX If aListItemElement is <dl> or <dd> and current parent is <ul> or <ol>,
10705 // the list items won't be unwrapped. If aListItemElement is <li> and its
10706 // current parent is <dl>, there is same issue.
10707 if (!HTMLEditUtils::IsAnyListElement(pointToInsertListItem.GetContainer()) &&
10708 HTMLEditUtils::IsListItem(&aListItemElement)) {
10709 Result<EditorDOMPoint, nsresult> unwrapOrphanListItemElementResult =
10710 RemoveBlockContainerWithTransaction(aListItemElement);
10711 if (MOZ_UNLIKELY(unwrapOrphanListItemElementResult.isErr())) {
10712 NS_WARNING("HTMLEditor::RemoveBlockContainerWithTransaction() failed");
10713 return unwrapOrphanListItemElementResult.unwrapErr();
10715 if (AllowsTransactionsToChangeSelection() &&
10716 unwrapOrphanListItemElementResult.inspect().IsSet()) {
10717 pointToPutCaret = unwrapOrphanListItemElementResult.unwrap();
10719 if (!pointToPutCaret.IsSet()) {
10720 return NS_OK;
10722 nsresult rv = CollapseSelectionTo(pointToPutCaret);
10723 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
10724 "EditorBase::CollapseSelectionTo() failed");
10725 return rv;
10728 if (pointToPutCaret.IsSet()) {
10729 nsresult rv = CollapseSelectionTo(pointToPutCaret);
10730 if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) {
10731 NS_WARNING("EditorBase::CollapseSelectionTo() failed");
10732 return rv;
10734 NS_WARNING_ASSERTION(
10735 NS_SUCCEEDED(rv),
10736 "EditorBase::CollapseSelectionTo() failed, but ignored");
10739 if (aLiftUpFromAllParentListElements == LiftUpFromAllParentListElements::No) {
10740 return NS_OK;
10742 // XXX If aListItemElement is moved to unexpected element by mutation event
10743 // listener, shouldn't we stop calling this?
10744 nsresult rv = LiftUpListItemElement(aListItemElement,
10745 LiftUpFromAllParentListElements::Yes);
10746 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
10747 "HTMLEditor::LiftUpListItemElement("
10748 "LiftUpFromAllParentListElements::Yes) failed");
10749 return rv;
10752 nsresult HTMLEditor::DestroyListStructureRecursively(Element& aListElement) {
10753 MOZ_ASSERT(IsEditActionDataAvailable());
10754 MOZ_ASSERT(HTMLEditUtils::IsAnyListElement(&aListElement));
10756 // XXX If mutation event listener inserts new child into `aListElement`,
10757 // this becomes infinite loop so that we should set limit of the
10758 // loop count from original child count.
10759 while (aListElement.GetFirstChild()) {
10760 OwningNonNull<nsIContent> child = *aListElement.GetFirstChild();
10762 if (HTMLEditUtils::IsListItem(child)) {
10763 // XXX Using LiftUpListItemElement() is too expensive for this purpose.
10764 // Looks like the reason why this method uses it is, only this loop
10765 // wants to work with first child of aListElement. However, what it
10766 // actually does is removing <li> as container. Perhaps, we should
10767 // decide destination first, and then, move contents in `child`.
10768 // XXX If aListElement is is a child of another list element (although
10769 // it's invalid tree), this moves the list item to outside of
10770 // aListElement's parent. Is that really intentional behavior?
10771 nsresult rv = LiftUpListItemElement(
10772 MOZ_KnownLive(*child->AsElement()),
10773 HTMLEditor::LiftUpFromAllParentListElements::Yes);
10774 if (NS_FAILED(rv)) {
10775 NS_WARNING(
10776 "HTMLEditor::LiftUpListItemElement(LiftUpFromAllParentListElements:"
10777 ":Yes) failed");
10778 return rv;
10780 continue;
10783 if (HTMLEditUtils::IsAnyListElement(child)) {
10784 nsresult rv =
10785 DestroyListStructureRecursively(MOZ_KnownLive(*child->AsElement()));
10786 if (NS_FAILED(rv)) {
10787 NS_WARNING("HTMLEditor::DestroyListStructureRecursively() failed");
10788 return rv;
10790 continue;
10793 // Delete any non-list items for now
10794 // XXX This is not HTML5 aware. HTML5 allows all list elements to have
10795 // <script> and <template> and <dl> element to have <div> to group
10796 // some <dt> and <dd> elements. So, this may break valid children.
10797 nsresult rv = DeleteNodeWithTransaction(*child);
10798 if (NS_FAILED(rv)) {
10799 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
10800 return rv;
10804 // Delete the now-empty list
10805 const Result<EditorDOMPoint, nsresult> unwrapListElementResult =
10806 RemoveBlockContainerWithTransaction(aListElement);
10807 if (MOZ_UNLIKELY(unwrapListElementResult.isErr())) {
10808 NS_WARNING("HTMLEditor::RemoveBlockContainerWithTransaction() failed");
10809 return unwrapListElementResult.inspectErr();
10811 const EditorDOMPoint& pointToPutCaret = unwrapListElementResult.inspect();
10812 if (!AllowsTransactionsToChangeSelection() || !pointToPutCaret.IsSet()) {
10813 return NS_OK;
10815 nsresult rv = CollapseSelectionTo(pointToPutCaret);
10816 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
10817 "EditorBase::CollapseSelectionTo() failed");
10818 return rv;
10821 nsresult HTMLEditor::EnsureSelectionInBodyOrDocumentElement() {
10822 MOZ_ASSERT(IsEditActionDataAvailable());
10824 RefPtr<Element> bodyOrDocumentElement = GetRoot();
10825 if (NS_WARN_IF(!bodyOrDocumentElement)) {
10826 return NS_ERROR_FAILURE;
10829 const auto atCaret = GetFirstSelectionStartPoint<EditorRawDOMPoint>();
10830 if (NS_WARN_IF(!atCaret.IsSet())) {
10831 return NS_ERROR_FAILURE;
10834 // XXX This does wrong things. Web apps can put any elements as sibling
10835 // of `<body>` element. Therefore, this collapses `Selection` into
10836 // the `<body>` element which `HTMLDocument.body` is set to. So,
10837 // this makes users impossible to modify content outside of the
10838 // `<body>` element even if caret is in an editing host.
10840 // Check that selection start container is inside the <body> element.
10841 // XXXsmaug this code is insane.
10842 nsINode* temp = atCaret.GetContainer();
10843 while (temp && !temp->IsHTMLElement(nsGkAtoms::body)) {
10844 temp = temp->GetParentOrShadowHostNode();
10847 // If we aren't in the <body> element, force the issue.
10848 if (!temp) {
10849 nsresult rv = CollapseSelectionToStartOf(*bodyOrDocumentElement);
10850 if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) {
10851 NS_WARNING(
10852 "EditorBase::CollapseSelectionToStartOf() caused destroying the "
10853 "editor");
10854 return NS_ERROR_EDITOR_DESTROYED;
10856 NS_WARNING_ASSERTION(
10857 NS_SUCCEEDED(rv),
10858 "EditorBase::CollapseSelectionToStartOf() failed, but ignored");
10859 return NS_OK;
10862 const auto selectionEndPoint = GetFirstSelectionEndPoint<EditorRawDOMPoint>();
10863 if (NS_WARN_IF(!selectionEndPoint.IsSet())) {
10864 return NS_ERROR_FAILURE;
10867 // check that selNode is inside body
10868 // XXXsmaug this code is insane.
10869 temp = selectionEndPoint.GetContainer();
10870 while (temp && !temp->IsHTMLElement(nsGkAtoms::body)) {
10871 temp = temp->GetParentOrShadowHostNode();
10874 // If we aren't in the <body> element, force the issue.
10875 if (!temp) {
10876 nsresult rv = CollapseSelectionToStartOf(*bodyOrDocumentElement);
10877 if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) {
10878 NS_WARNING(
10879 "EditorBase::CollapseSelectionToStartOf() caused destroying the "
10880 "editor");
10881 return NS_ERROR_EDITOR_DESTROYED;
10883 NS_WARNING_ASSERTION(
10884 NS_SUCCEEDED(rv),
10885 "EditorBase::CollapseSelectionToStartOf() failed, but ignored");
10888 return NS_OK;
10891 nsresult HTMLEditor::InsertPaddingBRElementForEmptyLastLineIfNeeded(
10892 Element& aElement) {
10893 MOZ_ASSERT(IsEditActionDataAvailable());
10895 if (!HTMLEditUtils::IsBlockElement(
10896 aElement, BlockInlineCheck::UseComputedDisplayStyle)) {
10897 return NS_OK;
10900 if (!HTMLEditUtils::IsEmptyNode(
10901 aElement, {EmptyCheckOption::TreatSingleBRElementAsVisible,
10902 EmptyCheckOption::TreatNonEditableContentAsInvisible})) {
10903 return NS_OK;
10906 Result<CreateElementResult, nsresult> insertPaddingBRElementResult =
10907 InsertPaddingBRElementForEmptyLastLineWithTransaction(
10908 EditorDOMPoint(&aElement, 0u));
10909 if (MOZ_UNLIKELY(insertPaddingBRElementResult.isErr())) {
10910 NS_WARNING(
10911 "HTMLEditor::InsertPaddingBRElementForEmptyLastLineWithTransaction() "
10912 "failed");
10913 return insertPaddingBRElementResult.unwrapErr();
10915 nsresult rv = insertPaddingBRElementResult.inspect().SuggestCaretPointTo(
10916 *this, {SuggestCaret::OnlyIfHasSuggestion,
10917 SuggestCaret::OnlyIfTransactionsAllowedToDoIt,
10918 SuggestCaret::AndIgnoreTrivialError});
10919 if (NS_FAILED(rv)) {
10920 NS_WARNING("CreateElementResult::SuggestCaretPointTo() failed");
10921 return rv;
10923 NS_WARNING_ASSERTION(
10924 rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
10925 "CreateElementResult::SuggestCaretPointTo() failed, but ignored");
10926 return NS_OK;
10929 Result<EditorDOMPoint, nsresult> HTMLEditor::RemoveAlignFromDescendants(
10930 Element& aElement, const nsAString& aAlignType, EditTarget aEditTarget) {
10931 MOZ_ASSERT(IsEditActionDataAvailable());
10932 MOZ_ASSERT(!aElement.IsHTMLElement(nsGkAtoms::table));
10934 const bool useCSS = IsCSSEnabled();
10936 EditorDOMPoint pointToPutCaret;
10938 // Let's remove all alignment hints in the children of aNode; it can
10939 // be an ALIGN attribute (in case we just remove it) or a CENTER
10940 // element (here we have to remove the container and keep its
10941 // children). We break on tables and don't look at their children.
10942 nsCOMPtr<nsIContent> nextSibling;
10943 for (nsIContent* content =
10944 aEditTarget == EditTarget::NodeAndDescendantsExceptTable
10945 ? &aElement
10946 : aElement.GetFirstChild();
10947 content; content = nextSibling) {
10948 // Get the next sibling before removing content from the DOM tree.
10949 // XXX If next sibling is removed from the parent and/or inserted to
10950 // different parent, we will behave unexpectedly. I think that
10951 // we should create child list and handle it with checking whether
10952 // it's still a child of expected parent.
10953 nextSibling = aEditTarget == EditTarget::NodeAndDescendantsExceptTable
10954 ? nullptr
10955 : content->GetNextSibling();
10957 if (content->IsHTMLElement(nsGkAtoms::center)) {
10958 OwningNonNull<Element> centerElement = *content->AsElement();
10960 Result<EditorDOMPoint, nsresult> pointToPutCaretOrError =
10961 RemoveAlignFromDescendants(centerElement, aAlignType,
10962 EditTarget::OnlyDescendantsExceptTable);
10963 if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) {
10964 NS_WARNING(
10965 "HTMLEditor::RemoveAlignFromDescendants(EditTarget::"
10966 "OnlyDescendantsExceptTable) failed");
10967 return pointToPutCaretOrError;
10969 if (pointToPutCaretOrError.inspect().IsSet()) {
10970 pointToPutCaret = pointToPutCaretOrError.unwrap();
10974 // We may have to insert a `<br>` element before first child of the
10975 // `<center>` element because it should be first element of a hard line
10976 // even after removing the `<center>` element.
10978 Result<CreateElementResult, nsresult>
10979 maybeInsertBRElementBeforeFirstChildResult =
10980 EnsureHardLineBeginsWithFirstChildOf(centerElement);
10981 if (MOZ_UNLIKELY(maybeInsertBRElementBeforeFirstChildResult.isErr())) {
10982 NS_WARNING(
10983 "HTMLEditor::EnsureHardLineBeginsWithFirstChildOf() failed");
10984 return maybeInsertBRElementBeforeFirstChildResult.propagateErr();
10986 CreateElementResult unwrappedResult =
10987 maybeInsertBRElementBeforeFirstChildResult.unwrap();
10988 if (unwrappedResult.HasCaretPointSuggestion()) {
10989 pointToPutCaret = unwrappedResult.UnwrapCaretPoint();
10993 // We may have to insert a `<br>` element after last child of the
10994 // `<center>` element because it should be last element of a hard line
10995 // even after removing the `<center>` element.
10997 Result<CreateElementResult, nsresult>
10998 maybeInsertBRElementAfterLastChildResult =
10999 EnsureHardLineEndsWithLastChildOf(centerElement);
11000 if (MOZ_UNLIKELY(maybeInsertBRElementAfterLastChildResult.isErr())) {
11001 NS_WARNING("HTMLEditor::EnsureHardLineEndsWithLastChildOf() failed");
11002 return maybeInsertBRElementAfterLastChildResult.propagateErr();
11004 CreateElementResult unwrappedResult =
11005 maybeInsertBRElementAfterLastChildResult.unwrap();
11006 if (unwrappedResult.HasCaretPointSuggestion()) {
11007 pointToPutCaret = unwrappedResult.UnwrapCaretPoint();
11012 Result<EditorDOMPoint, nsresult> unwrapCenterElementResult =
11013 RemoveContainerWithTransaction(centerElement);
11014 if (MOZ_UNLIKELY(unwrapCenterElementResult.isErr())) {
11015 NS_WARNING("HTMLEditor::RemoveContainerWithTransaction() failed");
11016 return unwrapCenterElementResult;
11018 if (unwrapCenterElementResult.inspect().IsSet()) {
11019 pointToPutCaret = unwrapCenterElementResult.unwrap();
11022 continue;
11025 if (!HTMLEditUtils::IsBlockElement(*content,
11026 BlockInlineCheck::UseHTMLDefaultStyle) &&
11027 !content->IsHTMLElement(nsGkAtoms::hr)) {
11028 continue;
11031 const OwningNonNull<Element> blockOrHRElement = *content->AsElement();
11032 if (HTMLEditUtils::SupportsAlignAttr(blockOrHRElement)) {
11033 nsresult rv =
11034 RemoveAttributeWithTransaction(blockOrHRElement, *nsGkAtoms::align);
11035 if (NS_FAILED(rv)) {
11036 NS_WARNING(
11037 "EditorBase::RemoveAttributeWithTransaction(nsGkAtoms::align) "
11038 "failed");
11039 return Err(rv);
11042 if (useCSS) {
11043 if (blockOrHRElement->IsAnyOfHTMLElements(nsGkAtoms::table,
11044 nsGkAtoms::hr)) {
11045 nsresult rv = SetAttributeOrEquivalent(
11046 blockOrHRElement, nsGkAtoms::align, aAlignType, false);
11047 if (NS_WARN_IF(Destroyed())) {
11048 return Err(NS_ERROR_EDITOR_DESTROYED);
11050 if (NS_FAILED(rv)) {
11051 NS_WARNING(
11052 "EditorBase::SetAttributeOrEquivalent(nsGkAtoms::align) failed");
11053 return Err(rv);
11055 } else {
11056 nsStyledElement* styledBlockOrHRElement =
11057 nsStyledElement::FromNode(blockOrHRElement);
11058 if (NS_WARN_IF(!styledBlockOrHRElement)) {
11059 return Err(NS_ERROR_FAILURE);
11061 // MOZ_KnownLive(*styledBlockOrHRElement): It's `blockOrHRElement
11062 // which is OwningNonNull.
11063 nsAutoString dummyCssValue;
11064 Result<EditorDOMPoint, nsresult> pointToPutCaretOrError =
11065 CSSEditUtils::RemoveCSSInlineStyleWithTransaction(
11066 *this, MOZ_KnownLive(*styledBlockOrHRElement),
11067 nsGkAtoms::textAlign, dummyCssValue);
11068 if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) {
11069 NS_WARNING(
11070 "CSSEditUtils::RemoveCSSInlineStyleWithTransaction(nsGkAtoms::"
11071 "textAlign) failed");
11072 return pointToPutCaretOrError;
11074 if (pointToPutCaretOrError.inspect().IsSet()) {
11075 pointToPutCaret = pointToPutCaretOrError.unwrap();
11079 if (!blockOrHRElement->IsHTMLElement(nsGkAtoms::table)) {
11080 // unless this is a table, look at children
11081 Result<EditorDOMPoint, nsresult> pointToPutCaretOrError =
11082 RemoveAlignFromDescendants(blockOrHRElement, aAlignType,
11083 EditTarget::OnlyDescendantsExceptTable);
11084 if (pointToPutCaretOrError.isErr()) {
11085 NS_WARNING(
11086 "HTMLEditor::RemoveAlignFromDescendants(EditTarget::"
11087 "OnlyDescendantsExceptTable) failed");
11088 return pointToPutCaretOrError;
11090 if (pointToPutCaretOrError.inspect().IsSet()) {
11091 pointToPutCaret = pointToPutCaretOrError.unwrap();
11095 return pointToPutCaret;
11098 Result<CreateElementResult, nsresult>
11099 HTMLEditor::EnsureHardLineBeginsWithFirstChildOf(
11100 Element& aRemovingContainerElement) {
11101 MOZ_ASSERT(IsEditActionDataAvailable());
11103 nsIContent* firstEditableChild = HTMLEditUtils::GetFirstChild(
11104 aRemovingContainerElement, {WalkTreeOption::IgnoreNonEditableNode});
11105 if (!firstEditableChild) {
11106 return CreateElementResult::NotHandled();
11109 if (HTMLEditUtils::IsBlockElement(
11110 *firstEditableChild, BlockInlineCheck::UseComputedDisplayStyle) ||
11111 firstEditableChild->IsHTMLElement(nsGkAtoms::br)) {
11112 return CreateElementResult::NotHandled();
11115 nsIContent* previousEditableContent = HTMLEditUtils::GetPreviousSibling(
11116 aRemovingContainerElement, {WalkTreeOption::IgnoreNonEditableNode});
11117 if (!previousEditableContent) {
11118 return CreateElementResult::NotHandled();
11121 if (HTMLEditUtils::IsBlockElement(
11122 *previousEditableContent,
11123 BlockInlineCheck::UseComputedDisplayStyle) ||
11124 previousEditableContent->IsHTMLElement(nsGkAtoms::br)) {
11125 return CreateElementResult::NotHandled();
11128 Result<CreateElementResult, nsresult> insertBRElementResult = InsertBRElement(
11129 WithTransaction::Yes, EditorDOMPoint(&aRemovingContainerElement, 0u));
11130 NS_WARNING_ASSERTION(
11131 insertBRElementResult.isOk(),
11132 "HTMLEditor::InsertBRElement(WithTransaction::Yes) failed");
11133 return insertBRElementResult;
11136 Result<CreateElementResult, nsresult>
11137 HTMLEditor::EnsureHardLineEndsWithLastChildOf(
11138 Element& aRemovingContainerElement) {
11139 MOZ_ASSERT(IsEditActionDataAvailable());
11141 nsIContent* firstEditableContent = HTMLEditUtils::GetLastChild(
11142 aRemovingContainerElement, {WalkTreeOption::IgnoreNonEditableNode});
11143 if (!firstEditableContent) {
11144 return CreateElementResult::NotHandled();
11147 if (HTMLEditUtils::IsBlockElement(
11148 *firstEditableContent, BlockInlineCheck::UseComputedDisplayStyle) ||
11149 firstEditableContent->IsHTMLElement(nsGkAtoms::br)) {
11150 return CreateElementResult::NotHandled();
11153 nsIContent* nextEditableContent = HTMLEditUtils::GetPreviousSibling(
11154 aRemovingContainerElement, {WalkTreeOption::IgnoreNonEditableNode});
11155 if (!nextEditableContent) {
11156 return CreateElementResult::NotHandled();
11159 if (HTMLEditUtils::IsBlockElement(
11160 *nextEditableContent, BlockInlineCheck::UseComputedDisplayStyle) ||
11161 nextEditableContent->IsHTMLElement(nsGkAtoms::br)) {
11162 return CreateElementResult::NotHandled();
11165 Result<CreateElementResult, nsresult> insertBRElementResult = InsertBRElement(
11166 WithTransaction::Yes, EditorDOMPoint::AtEndOf(aRemovingContainerElement));
11167 NS_WARNING_ASSERTION(
11168 insertBRElementResult.isOk(),
11169 "HTMLEditor::InsertBRElement(WithTransaction::Yes) failed");
11170 return insertBRElementResult;
11173 Result<EditorDOMPoint, nsresult> HTMLEditor::SetBlockElementAlign(
11174 Element& aBlockOrHRElement, const nsAString& aAlignType,
11175 EditTarget aEditTarget) {
11176 MOZ_ASSERT(IsEditActionDataAvailable());
11177 MOZ_ASSERT(HTMLEditUtils::IsBlockElement(
11178 aBlockOrHRElement, BlockInlineCheck::UseHTMLDefaultStyle) ||
11179 aBlockOrHRElement.IsHTMLElement(nsGkAtoms::hr));
11180 MOZ_ASSERT(IsCSSEnabled() ||
11181 HTMLEditUtils::SupportsAlignAttr(aBlockOrHRElement));
11183 EditorDOMPoint pointToPutCaret;
11184 if (!aBlockOrHRElement.IsHTMLElement(nsGkAtoms::table)) {
11185 Result<EditorDOMPoint, nsresult> pointToPutCaretOrError =
11186 RemoveAlignFromDescendants(aBlockOrHRElement, aAlignType, aEditTarget);
11187 if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) {
11188 NS_WARNING("HTMLEditor::RemoveAlignFromDescendants() failed");
11189 return pointToPutCaretOrError;
11191 if (pointToPutCaretOrError.inspect().IsSet()) {
11192 pointToPutCaret = pointToPutCaretOrError.unwrap();
11195 nsresult rv = SetAttributeOrEquivalent(&aBlockOrHRElement, nsGkAtoms::align,
11196 aAlignType, false);
11197 if (NS_WARN_IF(Destroyed())) {
11198 return Err(NS_ERROR_EDITOR_DESTROYED);
11200 if (NS_FAILED(rv)) {
11201 NS_WARNING("HTMLEditor::SetAttributeOrEquivalent(nsGkAtoms::align) failed");
11202 return Err(rv);
11204 return pointToPutCaret;
11207 Result<EditorDOMPoint, nsresult> HTMLEditor::ChangeMarginStart(
11208 Element& aElement, ChangeMargin aChangeMargin,
11209 const Element& aEditingHost) {
11210 MOZ_ASSERT(IsEditActionDataAvailable());
11212 nsStaticAtom& marginProperty = MarginPropertyAtomForIndent(aElement);
11213 if (NS_WARN_IF(Destroyed())) {
11214 return Err(NS_ERROR_EDITOR_DESTROYED);
11216 nsAutoString value;
11217 DebugOnly<nsresult> rvIgnored =
11218 CSSEditUtils::GetSpecifiedProperty(aElement, marginProperty, value);
11219 if (NS_WARN_IF(Destroyed())) {
11220 return Err(NS_ERROR_EDITOR_DESTROYED);
11222 NS_WARNING_ASSERTION(
11223 NS_SUCCEEDED(rvIgnored),
11224 "CSSEditUtils::GetSpecifiedProperty() failed, but ignored");
11225 float f;
11226 RefPtr<nsAtom> unit;
11227 CSSEditUtils::ParseLength(value, &f, getter_AddRefs(unit));
11228 if (!f) {
11229 unit = nsGkAtoms::px;
11231 int8_t multiplier = aChangeMargin == ChangeMargin::Increase ? 1 : -1;
11232 if (nsGkAtoms::in == unit) {
11233 f += NS_EDITOR_INDENT_INCREMENT_IN * multiplier;
11234 } else if (nsGkAtoms::cm == unit) {
11235 f += NS_EDITOR_INDENT_INCREMENT_CM * multiplier;
11236 } else if (nsGkAtoms::mm == unit) {
11237 f += NS_EDITOR_INDENT_INCREMENT_MM * multiplier;
11238 } else if (nsGkAtoms::pt == unit) {
11239 f += NS_EDITOR_INDENT_INCREMENT_PT * multiplier;
11240 } else if (nsGkAtoms::pc == unit) {
11241 f += NS_EDITOR_INDENT_INCREMENT_PC * multiplier;
11242 } else if (nsGkAtoms::em == unit) {
11243 f += NS_EDITOR_INDENT_INCREMENT_EM * multiplier;
11244 } else if (nsGkAtoms::ex == unit) {
11245 f += NS_EDITOR_INDENT_INCREMENT_EX * multiplier;
11246 } else if (nsGkAtoms::px == unit) {
11247 f += NS_EDITOR_INDENT_INCREMENT_PX * multiplier;
11248 } else if (nsGkAtoms::percentage == unit) {
11249 f += NS_EDITOR_INDENT_INCREMENT_PERCENT * multiplier;
11252 if (0 < f) {
11253 if (nsStyledElement* styledElement = nsStyledElement::FromNode(&aElement)) {
11254 nsAutoString newValue;
11255 newValue.AppendFloat(f);
11256 newValue.Append(nsDependentAtomString(unit));
11257 // MOZ_KnownLive(*styledElement): It's aElement and its lifetime must
11258 // be guaranteed by caller because of MOZ_CAN_RUN_SCRIPT method.
11259 // MOZ_KnownLive(merginProperty): It's nsStaticAtom.
11260 nsresult rv = CSSEditUtils::SetCSSPropertyWithTransaction(
11261 *this, MOZ_KnownLive(*styledElement), MOZ_KnownLive(marginProperty),
11262 newValue);
11263 if (rv == NS_ERROR_EDITOR_DESTROYED) {
11264 NS_WARNING(
11265 "CSSEditUtils::SetCSSPropertyWithTransaction() destroyed the "
11266 "editor");
11267 return Err(NS_ERROR_EDITOR_DESTROYED);
11269 NS_WARNING_ASSERTION(
11270 NS_SUCCEEDED(rv),
11271 "CSSEditUtils::SetCSSPropertyWithTransaction() failed, but ignored");
11273 return EditorDOMPoint();
11276 if (nsStyledElement* styledElement = nsStyledElement::FromNode(&aElement)) {
11277 // MOZ_KnownLive(*styledElement): It's aElement and its lifetime must
11278 // be guaranteed by caller because of MOZ_CAN_RUN_SCRIPT method.
11279 // MOZ_KnownLive(merginProperty): It's nsStaticAtom.
11280 nsresult rv = CSSEditUtils::RemoveCSSPropertyWithTransaction(
11281 *this, MOZ_KnownLive(*styledElement), MOZ_KnownLive(marginProperty),
11282 value);
11283 if (rv == NS_ERROR_EDITOR_DESTROYED) {
11284 NS_WARNING(
11285 "CSSEditUtils::RemoveCSSPropertyWithTransaction() destroyed the "
11286 "editor");
11287 return Err(NS_ERROR_EDITOR_DESTROYED);
11289 NS_WARNING_ASSERTION(
11290 NS_SUCCEEDED(rv),
11291 "CSSEditUtils::RemoveCSSPropertyWithTransaction() failed, but ignored");
11294 // Remove unnecessary divs
11295 if (!aElement.IsHTMLElement(nsGkAtoms::div) ||
11296 HTMLEditUtils::ElementHasAttribute(aElement)) {
11297 return EditorDOMPoint();
11299 // Don't touch editing host nor node which is outside of it.
11300 if (&aElement == &aEditingHost ||
11301 !aElement.IsInclusiveDescendantOf(&aEditingHost)) {
11302 return EditorDOMPoint();
11305 Result<EditorDOMPoint, nsresult> unwrapDivElementResult =
11306 RemoveContainerWithTransaction(aElement);
11307 NS_WARNING_ASSERTION(unwrapDivElementResult.isOk(),
11308 "HTMLEditor::RemoveContainerWithTransaction() failed");
11309 return unwrapDivElementResult;
11312 Result<EditActionResult, nsresult>
11313 HTMLEditor::SetSelectionToAbsoluteAsSubAction(const Element& aEditingHost) {
11314 AutoPlaceholderBatch treatAsOneTransaction(
11315 *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
11316 IgnoredErrorResult ignoredError;
11317 AutoEditSubActionNotifier startToHandleEditSubAction(
11318 *this, EditSubAction::eSetPositionToAbsolute, nsIEditor::eNext,
11319 ignoredError);
11320 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
11321 return Err(ignoredError.StealNSResult());
11323 NS_WARNING_ASSERTION(
11324 !ignoredError.Failed(),
11325 "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
11328 Result<EditActionResult, nsresult> result = CanHandleHTMLEditSubAction();
11329 if (MOZ_UNLIKELY(result.isErr())) {
11330 NS_WARNING("HTMLEditor::CanHandleHTMLEditSubAction() failed");
11331 return result;
11333 if (result.inspect().Canceled()) {
11334 return result;
11338 nsresult rv = EnsureNoPaddingBRElementForEmptyEditor();
11339 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
11340 return Err(NS_ERROR_EDITOR_DESTROYED);
11342 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
11343 "EditorBase::EnsureNoPaddingBRElementForEmptyEditor() "
11344 "failed, but ignored");
11346 if (NS_SUCCEEDED(rv) && SelectionRef().IsCollapsed()) {
11347 nsresult rv = EnsureCaretNotAfterInvisibleBRElement();
11348 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
11349 return Err(NS_ERROR_EDITOR_DESTROYED);
11351 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
11352 "HTMLEditor::EnsureCaretNotAfterInvisibleBRElement() "
11353 "failed, but ignored");
11354 if (NS_SUCCEEDED(rv)) {
11355 nsresult rv = PrepareInlineStylesForCaret();
11356 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
11357 return Err(NS_ERROR_EDITOR_DESTROYED);
11359 NS_WARNING_ASSERTION(
11360 NS_SUCCEEDED(rv),
11361 "HTMLEditor::PrepareInlineStylesForCaret() failed, but ignored");
11365 auto EnsureCaretInElementIfCollapsedOutside =
11366 [&](Element& aElement) MOZ_CAN_RUN_SCRIPT {
11367 if (!SelectionRef().IsCollapsed() || !SelectionRef().RangeCount()) {
11368 return NS_OK;
11370 const auto firstRangeStartPoint =
11371 GetFirstSelectionStartPoint<EditorRawDOMPoint>();
11372 if (MOZ_UNLIKELY(!firstRangeStartPoint.IsSet())) {
11373 return NS_OK;
11375 const Result<EditorRawDOMPoint, nsresult> pointToPutCaretOrError =
11376 HTMLEditUtils::ComputePointToPutCaretInElementIfOutside<
11377 EditorRawDOMPoint>(aElement, firstRangeStartPoint);
11378 if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) {
11379 NS_WARNING(
11380 "HTMLEditUtils::ComputePointToPutCaretInElementIfOutside() "
11381 "failed, but ignored");
11382 return NS_OK;
11384 if (!pointToPutCaretOrError.inspect().IsSet()) {
11385 return NS_OK;
11387 nsresult rv = CollapseSelectionTo(pointToPutCaretOrError.inspect());
11388 if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) {
11389 NS_WARNING("EditorBase::CollapseSelectionTo() failed");
11390 return NS_ERROR_EDITOR_DESTROYED;
11392 NS_WARNING_ASSERTION(
11393 NS_SUCCEEDED(rv),
11394 "EditorBase::CollapseSelectionTo() failed, but ignored");
11395 return NS_OK;
11398 RefPtr<Element> focusElement = GetSelectionContainerElement();
11399 if (focusElement && HTMLEditUtils::IsImage(focusElement)) {
11400 nsresult rv = EnsureCaretInElementIfCollapsedOutside(*focusElement);
11401 if (NS_FAILED(rv)) {
11402 NS_WARNING("EnsureCaretInElementIfCollapsedOutside() failed");
11403 return Err(rv);
11405 return EditActionResult::HandledResult();
11408 // XXX Why do we do this only when there is only one selection range?
11409 if (!SelectionRef().IsCollapsed() && SelectionRef().RangeCount() == 1u) {
11410 Result<EditorRawDOMRange, nsresult> extendedRange =
11411 GetRangeExtendedToHardLineEdgesForBlockEditAction(
11412 SelectionRef().GetRangeAt(0u), aEditingHost);
11413 if (MOZ_UNLIKELY(extendedRange.isErr())) {
11414 NS_WARNING(
11415 "HTMLEditor::GetRangeExtendedToHardLineEdgesForBlockEditAction() "
11416 "failed");
11417 return extendedRange.propagateErr();
11419 // Note that end point may be prior to start point. So, we
11420 // cannot use Selection::SetStartAndEndInLimit() here.
11421 IgnoredErrorResult error;
11422 SelectionRef().SetBaseAndExtentInLimiter(
11423 extendedRange.inspect().StartRef().ToRawRangeBoundary(),
11424 extendedRange.inspect().EndRef().ToRawRangeBoundary(), error);
11425 if (NS_WARN_IF(Destroyed())) {
11426 return Err(NS_ERROR_EDITOR_DESTROYED);
11428 if (MOZ_UNLIKELY(error.Failed())) {
11429 NS_WARNING("Selection::SetBaseAndExtentInLimiter() failed");
11430 return Err(error.StealNSResult());
11434 RefPtr<Element> divElement;
11435 rv = MoveSelectedContentsToDivElementToMakeItAbsolutePosition(
11436 address_of(divElement), aEditingHost);
11437 // MoveSelectedContentsToDivElementToMakeItAbsolutePosition() may restore
11438 // selection with AutoSelectionRestorer. Therefore, the editor might have
11439 // already been destroyed now.
11440 if (NS_WARN_IF(Destroyed())) {
11441 return Err(NS_ERROR_EDITOR_DESTROYED);
11443 if (NS_FAILED(rv)) {
11444 NS_WARNING(
11445 "HTMLEditor::MoveSelectedContentsToDivElementToMakeItAbsolutePosition()"
11446 " failed");
11447 return Err(rv);
11450 if (IsSelectionRangeContainerNotContent()) {
11451 NS_WARNING("Mutation event listener might have changed the selection");
11452 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
11455 rv = MaybeInsertPaddingBRElementForEmptyLastLineAtSelection();
11456 if (NS_FAILED(rv)) {
11457 NS_WARNING(
11458 "HTMLEditor::MaybeInsertPaddingBRElementForEmptyLastLineAtSelection() "
11459 "failed");
11460 return Err(rv);
11463 if (!divElement) {
11464 return EditActionResult::HandledResult();
11467 rv = SetPositionToAbsoluteOrStatic(*divElement, true);
11468 if (NS_WARN_IF(Destroyed())) {
11469 return Err(NS_ERROR_EDITOR_DESTROYED);
11471 if (NS_FAILED(rv)) {
11472 NS_WARNING("HTMLEditor::SetPositionToAbsoluteOrStatic() failed");
11473 return Err(rv);
11476 rv = EnsureCaretInElementIfCollapsedOutside(*divElement);
11477 if (NS_FAILED(rv)) {
11478 NS_WARNING("EnsureCaretInElementIfCollapsedOutside() failed");
11479 return Err(rv);
11481 return EditActionResult::HandledResult();
11484 nsresult HTMLEditor::MoveSelectedContentsToDivElementToMakeItAbsolutePosition(
11485 RefPtr<Element>* aTargetElement, const Element& aEditingHost) {
11486 MOZ_ASSERT(IsEditActionDataAvailable());
11487 MOZ_ASSERT(aTargetElement);
11489 AutoSelectionRestorer restoreSelectionLater(*this);
11491 EditorDOMPoint pointToPutCaret;
11493 // Use these ranges to construct a list of nodes to act on.
11494 AutoTArray<OwningNonNull<nsIContent>, 64> arrayOfContents;
11496 AutoRangeArray extendedSelectionRanges(SelectionRef());
11497 extendedSelectionRanges.ExtendRangesToWrapLines(
11498 EditSubAction::eSetPositionToAbsolute,
11499 BlockInlineCheck::UseHTMLDefaultStyle, aEditingHost);
11500 Result<EditorDOMPoint, nsresult> splitResult =
11501 extendedSelectionRanges
11502 .SplitTextAtEndBoundariesAndInlineAncestorsAtBothBoundaries(
11503 *this, BlockInlineCheck::UseHTMLDefaultStyle, aEditingHost);
11504 if (MOZ_UNLIKELY(splitResult.isErr())) {
11505 NS_WARNING(
11506 "AutoRangeArray::"
11507 "SplitTextAtEndBoundariesAndInlineAncestorsAtBothBoundaries() "
11508 "failed");
11509 return splitResult.unwrapErr();
11511 if (splitResult.inspect().IsSet()) {
11512 pointToPutCaret = splitResult.unwrap();
11514 nsresult rv = extendedSelectionRanges.CollectEditTargetNodes(
11515 *this, arrayOfContents, EditSubAction::eSetPositionToAbsolute,
11516 AutoRangeArray::CollectNonEditableNodes::Yes);
11517 if (NS_FAILED(rv)) {
11518 NS_WARNING(
11519 "AutoRangeArray::CollectEditTargetNodes(EditSubAction::"
11520 "eSetPositionToAbsolute, CollectNonEditableNodes::Yes) failed");
11521 return rv;
11525 Result<EditorDOMPoint, nsresult> splitAtBRElementsResult =
11526 MaybeSplitElementsAtEveryBRElement(arrayOfContents,
11527 EditSubAction::eSetPositionToAbsolute);
11528 if (MOZ_UNLIKELY(splitAtBRElementsResult.isErr())) {
11529 NS_WARNING(
11530 "HTMLEditor::MaybeSplitElementsAtEveryBRElement(EditSubAction::"
11531 "eSetPositionToAbsolute) failed");
11532 return splitAtBRElementsResult.inspectErr();
11534 if (splitAtBRElementsResult.inspect().IsSet()) {
11535 pointToPutCaret = splitAtBRElementsResult.unwrap();
11538 if (AllowsTransactionsToChangeSelection() &&
11539 pointToPutCaret.IsSetAndValid()) {
11540 nsresult rv = CollapseSelectionTo(pointToPutCaret);
11541 if (NS_FAILED(rv)) {
11542 NS_WARNING("EditorBase::CollapseSelectionTo() failed");
11543 return rv;
11547 // If there is no visible and editable nodes in the edit targets, make an
11548 // empty block.
11549 // XXX Isn't this odd if there are only non-editable visible nodes?
11550 if (HTMLEditUtils::IsEmptyOneHardLine(
11551 arrayOfContents, BlockInlineCheck::UseHTMLDefaultStyle)) {
11552 const auto atCaret =
11553 EditorBase::GetFirstSelectionStartPoint<EditorDOMPoint>();
11554 if (NS_WARN_IF(!atCaret.IsSet())) {
11555 return NS_ERROR_FAILURE;
11558 // Make sure we can put a block here.
11559 Result<CreateElementResult, nsresult> createNewDivElementResult =
11560 InsertElementWithSplittingAncestorsWithTransaction(
11561 *nsGkAtoms::div, atCaret, BRElementNextToSplitPoint::Keep,
11562 aEditingHost);
11563 if (MOZ_UNLIKELY(createNewDivElementResult.isErr())) {
11564 NS_WARNING(
11565 "HTMLEditor::InsertElementWithSplittingAncestorsWithTransaction("
11566 "nsGkAtoms::div) failed");
11567 return createNewDivElementResult.unwrapErr();
11569 CreateElementResult unwrappedCreateNewDivElementResult =
11570 createNewDivElementResult.unwrap();
11571 // We'll update selection after deleting the content nodes and nobody
11572 // refers selection until then. Therefore, we don't need to update
11573 // selection here.
11574 unwrappedCreateNewDivElementResult.IgnoreCaretPointSuggestion();
11575 RefPtr<Element> newDivElement =
11576 unwrappedCreateNewDivElementResult.UnwrapNewNode();
11577 MOZ_ASSERT(newDivElement);
11578 // Delete anything that was in the list of nodes
11579 // XXX We don't need to remove items from the array.
11580 for (OwningNonNull<nsIContent>& curNode : arrayOfContents) {
11581 // MOZ_KnownLive because 'arrayOfContents' is guaranteed to keep it alive.
11582 nsresult rv = DeleteNodeWithTransaction(MOZ_KnownLive(*curNode));
11583 if (NS_FAILED(rv)) {
11584 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
11585 return rv;
11588 // Don't restore the selection
11589 restoreSelectionLater.Abort();
11590 nsresult rv = CollapseSelectionToStartOf(*newDivElement);
11591 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
11592 "EditorBase::CollapseSelectionToStartOf() failed");
11593 *aTargetElement = std::move(newDivElement);
11594 return rv;
11597 // `<div>` element to be positioned absolutely. This may have already
11598 // existed or newly created by this method.
11599 RefPtr<Element> targetDivElement;
11600 // Newly created list element for moving selected list item elements into
11601 // targetDivElement. I.e., this is created in the `<div>` element.
11602 RefPtr<Element> createdListElement;
11603 // If we handle a parent list item element, this is set to it. In such case,
11604 // we should handle its children again.
11605 RefPtr<Element> handledListItemElement;
11606 for (OwningNonNull<nsIContent>& content : arrayOfContents) {
11607 // Here's where we actually figure out what to do.
11608 EditorDOMPoint atContent(content);
11609 if (NS_WARN_IF(!atContent.IsSet())) {
11610 return NS_ERROR_FAILURE; // XXX not continue??
11613 // Ignore all non-editable nodes. Leave them be.
11614 if (!EditorUtils::IsEditableContent(content, EditorType::HTML)) {
11615 continue;
11618 // If current node is a child of a list element, we need another list
11619 // element in absolute-positioned `<div>` element to avoid non-selected
11620 // list items are moved into the `<div>` element.
11621 if (HTMLEditUtils::IsAnyListElement(atContent.GetContainer())) {
11622 // If we cannot move current node to created list element, we need a
11623 // list element in the target `<div>` element for the destination.
11624 // Therefore, duplicate same list element into the target `<div>`
11625 // element.
11626 nsIContent* previousEditableContent =
11627 createdListElement
11628 ? HTMLEditUtils::GetPreviousSibling(
11629 content, {WalkTreeOption::IgnoreNonEditableNode})
11630 : nullptr;
11631 if (!createdListElement ||
11632 (previousEditableContent &&
11633 previousEditableContent != createdListElement)) {
11634 nsAtom* ULOrOLOrDLTagName =
11635 atContent.GetContainer()->NodeInfo()->NameAtom();
11636 if (targetDivElement) {
11637 // XXX Do we need to split the container? Since we'll append new
11638 // element at end of the <div> element.
11639 Result<SplitNodeResult, nsresult> splitNodeResult =
11640 MaybeSplitAncestorsForInsertWithTransaction(
11641 MOZ_KnownLive(*ULOrOLOrDLTagName), atContent, aEditingHost);
11642 if (MOZ_UNLIKELY(splitNodeResult.isErr())) {
11643 NS_WARNING(
11644 "HTMLEditor::MaybeSplitAncestorsForInsertWithTransaction() "
11645 "failed");
11646 return splitNodeResult.unwrapErr();
11648 // We'll update selection after creating a list element below.
11649 // Therefore, we don't need to touch selection here.
11650 splitNodeResult.inspect().IgnoreCaretPointSuggestion();
11651 } else {
11652 // If we've not had a target <div> element yet, let's insert a <div>
11653 // element with splitting the ancestors.
11654 Result<CreateElementResult, nsresult> createNewDivElementResult =
11655 InsertElementWithSplittingAncestorsWithTransaction(
11656 *nsGkAtoms::div, atContent, BRElementNextToSplitPoint::Keep,
11657 aEditingHost);
11658 if (MOZ_UNLIKELY(createNewDivElementResult.isErr())) {
11659 NS_WARNING(
11660 "HTMLEditor::"
11661 "InsertElementWithSplittingAncestorsWithTransaction(nsGkAtoms::"
11662 "div) failed");
11663 return createNewDivElementResult.unwrapErr();
11665 // We'll update selection after creating a list element below.
11666 // Therefor, we don't need to touch selection here.
11667 createNewDivElementResult.inspect().IgnoreCaretPointSuggestion();
11668 MOZ_ASSERT(createNewDivElementResult.inspect().GetNewNode());
11669 targetDivElement = createNewDivElementResult.unwrap().UnwrapNewNode();
11671 Result<CreateElementResult, nsresult> createNewListElementResult =
11672 CreateAndInsertElement(WithTransaction::Yes,
11673 MOZ_KnownLive(*ULOrOLOrDLTagName),
11674 EditorDOMPoint::AtEndOf(targetDivElement));
11675 if (MOZ_UNLIKELY(createNewListElementResult.isErr())) {
11676 NS_WARNING(
11677 "HTMLEditor::CreateAndInsertElement(WithTransaction::Yes) "
11678 "failed");
11679 return createNewListElementResult.unwrapErr();
11681 nsresult rv = createNewListElementResult.inspect().SuggestCaretPointTo(
11682 *this, {SuggestCaret::OnlyIfHasSuggestion,
11683 SuggestCaret::OnlyIfTransactionsAllowedToDoIt,
11684 SuggestCaret::AndIgnoreTrivialError});
11685 if (NS_FAILED(rv)) {
11686 NS_WARNING("CreateElementResult::SuggestCaretPointTo() failed");
11687 return Err(rv);
11689 NS_WARNING_ASSERTION(
11690 rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
11691 "CreateElementResult::SuggestCaretPointTo() failed, but ignored");
11692 createdListElement =
11693 createNewListElementResult.unwrap().UnwrapNewNode();
11694 MOZ_ASSERT(createdListElement);
11696 // Move current node (maybe, assumed as a list item element) into the
11697 // new list element in the target `<div>` element to be positioned
11698 // absolutely.
11699 // MOZ_KnownLive because 'arrayOfContents' is guaranteed to keep it alive.
11700 Result<MoveNodeResult, nsresult> moveNodeResult =
11701 MoveNodeToEndWithTransaction(MOZ_KnownLive(content),
11702 *createdListElement);
11703 if (MOZ_UNLIKELY(moveNodeResult.isErr())) {
11704 NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed");
11705 return moveNodeResult.propagateErr();
11707 nsresult rv = moveNodeResult.inspect().SuggestCaretPointTo(
11708 *this, {SuggestCaret::OnlyIfHasSuggestion,
11709 SuggestCaret::OnlyIfTransactionsAllowedToDoIt,
11710 SuggestCaret::AndIgnoreTrivialError});
11711 if (NS_FAILED(rv)) {
11712 NS_WARNING("MoveNodeResult::SuggestCaretPointTo() failed");
11713 return Err(rv);
11715 NS_WARNING_ASSERTION(
11716 rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
11717 "MoveNodeResult::SuggestCaretPointTo() failed, but ignored");
11718 continue;
11721 // If contents in a list item element is selected, we should move current
11722 // node into the target `<div>` element with the list item element itself
11723 // because we want to keep indent level of the contents.
11724 if (RefPtr<Element> listItemElement =
11725 HTMLEditUtils::GetClosestAncestorListItemElement(content,
11726 &aEditingHost)) {
11727 if (handledListItemElement == listItemElement) {
11728 // Current node has already been moved into the `<div>` element.
11729 continue;
11731 // If we cannot move the list item element into created list element,
11732 // we need another list element in the target `<div>` element.
11733 nsIContent* previousEditableContent =
11734 createdListElement
11735 ? HTMLEditUtils::GetPreviousSibling(
11736 *listItemElement, {WalkTreeOption::IgnoreNonEditableNode})
11737 : nullptr;
11738 if (!createdListElement ||
11739 (previousEditableContent &&
11740 previousEditableContent != createdListElement)) {
11741 EditorDOMPoint atListItem(listItemElement);
11742 if (NS_WARN_IF(!atListItem.IsSet())) {
11743 return NS_ERROR_FAILURE;
11745 // XXX If content is the listItemElement and not in a list element,
11746 // we duplicate wrong element into the target `<div>` element.
11747 nsAtom* containerName =
11748 atListItem.GetContainer()->NodeInfo()->NameAtom();
11749 if (targetDivElement) {
11750 // XXX Do we need to split the container? Since we'll append new
11751 // element at end of the <div> element.
11752 Result<SplitNodeResult, nsresult> splitNodeResult =
11753 MaybeSplitAncestorsForInsertWithTransaction(
11754 MOZ_KnownLive(*containerName), atListItem, aEditingHost);
11755 if (MOZ_UNLIKELY(splitNodeResult.isErr())) {
11756 NS_WARNING(
11757 "HTMLEditor::MaybeSplitAncestorsForInsertWithTransaction() "
11758 "failed");
11759 return splitNodeResult.unwrapErr();
11761 // We'll update selection after creating a list element below.
11762 // Therefore, we don't need to touch selection here.
11763 splitNodeResult.inspect().IgnoreCaretPointSuggestion();
11764 } else {
11765 // If we've not had a target <div> element yet, let's insert a <div>
11766 // element with splitting the ancestors.
11767 Result<CreateElementResult, nsresult> createNewDivElementResult =
11768 InsertElementWithSplittingAncestorsWithTransaction(
11769 *nsGkAtoms::div, atContent, BRElementNextToSplitPoint::Keep,
11770 aEditingHost);
11771 if (MOZ_UNLIKELY(createNewDivElementResult.isErr())) {
11772 NS_WARNING(
11773 "HTMLEditor::"
11774 "InsertElementWithSplittingAncestorsWithTransaction("
11775 "nsGkAtoms::div) failed");
11776 return createNewDivElementResult.unwrapErr();
11778 // We'll update selection after creating a list element below.
11779 // Therefore, we don't need to touch selection here.
11780 createNewDivElementResult.inspect().IgnoreCaretPointSuggestion();
11781 MOZ_ASSERT(createNewDivElementResult.inspect().GetNewNode());
11782 targetDivElement = createNewDivElementResult.unwrap().UnwrapNewNode();
11784 // XXX So, createdListElement may be set to a non-list element.
11785 Result<CreateElementResult, nsresult> createNewListElementResult =
11786 CreateAndInsertElement(WithTransaction::Yes,
11787 MOZ_KnownLive(*containerName),
11788 EditorDOMPoint::AtEndOf(targetDivElement));
11789 if (MOZ_UNLIKELY(createNewListElementResult.isErr())) {
11790 NS_WARNING(
11791 "HTMLEditor::CreateAndInsertElement(WithTransaction::Yes) "
11792 "failed");
11793 return createNewListElementResult.unwrapErr();
11795 nsresult rv = createNewListElementResult.inspect().SuggestCaretPointTo(
11796 *this, {SuggestCaret::OnlyIfHasSuggestion,
11797 SuggestCaret::OnlyIfTransactionsAllowedToDoIt,
11798 SuggestCaret::AndIgnoreTrivialError});
11799 if (NS_FAILED(rv)) {
11800 NS_WARNING("CreateElementResult::SuggestCaretPointTo() failed");
11801 return Err(rv);
11803 NS_WARNING_ASSERTION(
11804 rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
11805 "CreateElementResult::SuggestCaretPointTo() failed, but ignored");
11806 createdListElement =
11807 createNewListElementResult.unwrap().UnwrapNewNode();
11808 MOZ_ASSERT(createdListElement);
11810 // Move current list item element into the createdListElement (could be
11811 // non-list element due to the above bug) in a candidate `<div>` element
11812 // to be positioned absolutely.
11813 Result<MoveNodeResult, nsresult> moveListItemElementResult =
11814 MoveNodeToEndWithTransaction(*listItemElement, *createdListElement);
11815 if (MOZ_UNLIKELY(moveListItemElementResult.isErr())) {
11816 NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed");
11817 return moveListItemElementResult.unwrapErr();
11819 nsresult rv = moveListItemElementResult.inspect().SuggestCaretPointTo(
11820 *this, {SuggestCaret::OnlyIfHasSuggestion,
11821 SuggestCaret::OnlyIfTransactionsAllowedToDoIt,
11822 SuggestCaret::AndIgnoreTrivialError});
11823 if (NS_FAILED(rv)) {
11824 NS_WARNING("MoveNodeResult::SuggestCaretPointTo() failed");
11825 return Err(rv);
11827 NS_WARNING_ASSERTION(
11828 rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
11829 "MoveNodeResult::SuggestCaretPointTo() failed, but ignored");
11830 handledListItemElement = std::move(listItemElement);
11831 continue;
11834 if (!targetDivElement) {
11835 // If we meet a `<div>` element, use it as the absolute-position
11836 // container.
11837 // XXX This looks odd. If there are 2 or more `<div>` elements are
11838 // selected, first found `<div>` element will have all other
11839 // selected nodes.
11840 if (content->IsHTMLElement(nsGkAtoms::div)) {
11841 targetDivElement = content->AsElement();
11842 MOZ_ASSERT(!createdListElement);
11843 MOZ_ASSERT(!handledListItemElement);
11844 continue;
11846 // Otherwise, create new `<div>` element to be positioned absolutely
11847 // and to contain all selected nodes.
11848 Result<CreateElementResult, nsresult> createNewDivElementResult =
11849 InsertElementWithSplittingAncestorsWithTransaction(
11850 *nsGkAtoms::div, atContent, BRElementNextToSplitPoint::Keep,
11851 aEditingHost);
11852 if (MOZ_UNLIKELY(createNewDivElementResult.isErr())) {
11853 NS_WARNING(
11854 "HTMLEditor::InsertElementWithSplittingAncestorsWithTransaction("
11855 "nsGkAtoms::div) failed");
11856 return createNewDivElementResult.unwrapErr();
11858 nsresult rv = createNewDivElementResult.inspect().SuggestCaretPointTo(
11859 *this, {SuggestCaret::OnlyIfHasSuggestion,
11860 SuggestCaret::OnlyIfTransactionsAllowedToDoIt});
11861 if (NS_FAILED(rv)) {
11862 NS_WARNING("CreateElementResult::SuggestCaretPointTo() failed");
11863 return rv;
11865 MOZ_ASSERT(createNewDivElementResult.inspect().GetNewNode());
11866 targetDivElement = createNewDivElementResult.unwrap().UnwrapNewNode();
11869 // MOZ_KnownLive because 'arrayOfContents' is guaranteed to keep it alive.
11870 Result<MoveNodeResult, nsresult> moveNodeResult =
11871 MoveNodeToEndWithTransaction(MOZ_KnownLive(content), *targetDivElement);
11872 if (MOZ_UNLIKELY(moveNodeResult.isErr())) {
11873 NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed");
11874 return moveNodeResult.unwrapErr();
11876 nsresult rv = moveNodeResult.inspect().SuggestCaretPointTo(
11877 *this, {SuggestCaret::OnlyIfHasSuggestion,
11878 SuggestCaret::OnlyIfTransactionsAllowedToDoIt,
11879 SuggestCaret::AndIgnoreTrivialError});
11880 if (NS_FAILED(rv)) {
11881 NS_WARNING("MoveNodeResult::SuggestCaretPointTo() failed");
11882 return rv;
11884 NS_WARNING_ASSERTION(
11885 rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
11886 "MoveNodeResult::SuggestCaretPointTo() failed, but ignored");
11887 // Forget createdListElement, if any
11888 createdListElement = nullptr;
11890 *aTargetElement = std::move(targetDivElement);
11891 return NS_OK;
11894 Result<EditActionResult, nsresult>
11895 HTMLEditor::SetSelectionToStaticAsSubAction() {
11896 MOZ_ASSERT(IsEditActionDataAvailable());
11898 AutoPlaceholderBatch treatAsOneTransaction(
11899 *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
11900 IgnoredErrorResult ignoredError;
11901 AutoEditSubActionNotifier startToHandleEditSubAction(
11902 *this, EditSubAction::eSetPositionToStatic, nsIEditor::eNext,
11903 ignoredError);
11904 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
11905 return Err(ignoredError.StealNSResult());
11907 NS_WARNING_ASSERTION(
11908 !ignoredError.Failed(),
11909 "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
11912 Result<EditActionResult, nsresult> result = CanHandleHTMLEditSubAction();
11913 if (MOZ_UNLIKELY(result.isErr())) {
11914 NS_WARNING("HTMLEditor::CanHandleHTMLEditSubAction() failed");
11915 return result;
11917 if (result.inspect().Canceled()) {
11918 return result;
11922 nsresult rv = EnsureNoPaddingBRElementForEmptyEditor();
11923 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
11924 return Err(NS_ERROR_EDITOR_DESTROYED);
11926 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
11927 "EditorBase::EnsureNoPaddingBRElementForEmptyEditor() "
11928 "failed, but ignored");
11930 if (NS_SUCCEEDED(rv) && SelectionRef().IsCollapsed()) {
11931 nsresult rv = EnsureCaretNotAfterInvisibleBRElement();
11932 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
11933 return Err(NS_ERROR_EDITOR_DESTROYED);
11935 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
11936 "HTMLEditor::EnsureCaretNotAfterInvisibleBRElement() "
11937 "failed, but ignored");
11938 if (NS_SUCCEEDED(rv)) {
11939 nsresult rv = PrepareInlineStylesForCaret();
11940 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
11941 return Err(NS_ERROR_EDITOR_DESTROYED);
11943 NS_WARNING_ASSERTION(
11944 NS_SUCCEEDED(rv),
11945 "HTMLEditor::PrepareInlineStylesForCaret() failed, but ignored");
11949 RefPtr<Element> element = GetAbsolutelyPositionedSelectionContainer();
11950 if (!element) {
11951 if (NS_WARN_IF(Destroyed())) {
11952 return Err(NS_ERROR_EDITOR_DESTROYED);
11954 NS_WARNING(
11955 "HTMLEditor::GetAbsolutelyPositionedSelectionContainer() returned "
11956 "nullptr");
11957 return Err(NS_ERROR_FAILURE);
11961 AutoSelectionRestorer restoreSelectionLater(*this);
11963 nsresult rv = SetPositionToAbsoluteOrStatic(*element, false);
11964 if (NS_WARN_IF(Destroyed())) {
11965 return Err(NS_ERROR_EDITOR_DESTROYED);
11967 if (NS_FAILED(rv)) {
11968 NS_WARNING("HTMLEditor::SetPositionToAbsoluteOrStatic() failed");
11969 return Err(rv);
11973 // Restoring Selection might cause destroying the HTML editor.
11974 if (MOZ_UNLIKELY(Destroyed())) {
11975 NS_WARNING("Destroying AutoSelectionRestorer caused destroying the editor");
11976 return Err(NS_ERROR_EDITOR_DESTROYED);
11978 return EditActionResult::HandledResult();
11981 Result<EditActionResult, nsresult> HTMLEditor::AddZIndexAsSubAction(
11982 int32_t aChange) {
11983 MOZ_ASSERT(IsEditActionDataAvailable());
11985 AutoPlaceholderBatch treatAsOneTransaction(
11986 *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
11987 IgnoredErrorResult ignoredError;
11988 AutoEditSubActionNotifier startToHandleEditSubAction(
11989 *this,
11990 aChange < 0 ? EditSubAction::eDecreaseZIndex
11991 : EditSubAction::eIncreaseZIndex,
11992 nsIEditor::eNext, ignoredError);
11993 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
11994 return Err(ignoredError.StealNSResult());
11996 NS_WARNING_ASSERTION(
11997 !ignoredError.Failed(),
11998 "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
12001 Result<EditActionResult, nsresult> result = CanHandleHTMLEditSubAction();
12002 if (MOZ_UNLIKELY(result.isErr())) {
12003 NS_WARNING("HTMLEditor::CanHandleHTMLEditSubAction() failed");
12004 return result;
12006 if (result.inspect().Canceled()) {
12007 return result;
12011 nsresult rv = EnsureNoPaddingBRElementForEmptyEditor();
12012 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
12013 return Err(NS_ERROR_EDITOR_DESTROYED);
12015 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
12016 "EditorBase::EnsureNoPaddingBRElementForEmptyEditor() "
12017 "failed, but ignored");
12019 if (NS_SUCCEEDED(rv) && SelectionRef().IsCollapsed()) {
12020 nsresult rv = EnsureCaretNotAfterInvisibleBRElement();
12021 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
12022 return Err(NS_ERROR_EDITOR_DESTROYED);
12024 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
12025 "HTMLEditor::EnsureCaretNotAfterInvisibleBRElement() "
12026 "failed, but ignored");
12027 if (NS_SUCCEEDED(rv)) {
12028 nsresult rv = PrepareInlineStylesForCaret();
12029 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
12030 return Err(NS_ERROR_EDITOR_DESTROYED);
12032 NS_WARNING_ASSERTION(
12033 NS_SUCCEEDED(rv),
12034 "HTMLEditor::PrepareInlineStylesForCaret() failed, but ignored");
12038 RefPtr<Element> absolutelyPositionedElement =
12039 GetAbsolutelyPositionedSelectionContainer();
12040 if (!absolutelyPositionedElement) {
12041 if (NS_WARN_IF(Destroyed())) {
12042 return Err(NS_ERROR_EDITOR_DESTROYED);
12044 NS_WARNING(
12045 "HTMLEditor::GetAbsolutelyPositionedSelectionContainer() returned "
12046 "nullptr");
12047 return Err(NS_ERROR_FAILURE);
12050 nsStyledElement* absolutelyPositionedStyledElement =
12051 nsStyledElement::FromNode(absolutelyPositionedElement);
12052 if (NS_WARN_IF(!absolutelyPositionedStyledElement)) {
12053 return Err(NS_ERROR_FAILURE);
12057 AutoSelectionRestorer restoreSelectionLater(*this);
12059 // MOZ_KnownLive(*absolutelyPositionedStyledElement): It's
12060 // absolutelyPositionedElement whose type is RefPtr.
12061 Result<int32_t, nsresult> result = AddZIndexWithTransaction(
12062 MOZ_KnownLive(*absolutelyPositionedStyledElement), aChange);
12063 if (MOZ_UNLIKELY(result.isErr())) {
12064 NS_WARNING("HTMLEditor::AddZIndexWithTransaction() failed");
12065 return result.propagateErr();
12069 // Restoring Selection might cause destroying the HTML editor.
12070 if (MOZ_UNLIKELY(Destroyed())) {
12071 NS_WARNING("Destroying AutoSelectionRestorer caused destroying the editor");
12072 return Err(NS_ERROR_EDITOR_DESTROYED);
12075 return EditActionResult::HandledResult();
12078 nsresult HTMLEditor::OnDocumentModified() {
12079 if (mPendingDocumentModifiedRunner) {
12080 return NS_OK; // We've already posted same runnable into the queue.
12082 mPendingDocumentModifiedRunner = NewRunnableMethod(
12083 "HTMLEditor::OnModifyDocument", this, &HTMLEditor::OnModifyDocument);
12084 nsContentUtils::AddScriptRunner(do_AddRef(mPendingDocumentModifiedRunner));
12085 // Be aware, if OnModifyDocument() may be called synchronously, the
12086 // editor might have been destroyed here.
12087 return NS_WARN_IF(Destroyed()) ? NS_ERROR_EDITOR_DESTROYED : NS_OK;
12090 } // namespace mozilla