Bug 1921522: Mark WPTs gradient-external-reference.svg and pattern-external-reference...
[gecko.git] / editor / libeditor / HTMLEditSubActionHandler.cpp
blob5dd756a567dc80412639d940da1bf01e45c6431b
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 "AutoSelectionRestorer.h"
16 #include "CSSEditUtils.h"
17 #include "EditAction.h"
18 #include "EditorDOMPoint.h"
19 #include "EditorUtils.h"
20 #include "HTMLEditHelpers.h"
21 #include "HTMLEditUtils.h"
22 #include "PendingStyles.h" // for SpecifiedStyle
23 #include "WSRunObject.h"
25 #include "ErrorList.h"
26 #include "mozilla/Assertions.h"
27 #include "mozilla/Attributes.h"
28 #include "mozilla/AutoRestore.h"
29 #include "mozilla/CheckedInt.h"
30 #include "mozilla/ContentIterator.h"
31 #include "mozilla/EditorForwards.h"
32 #include "mozilla/IntegerRange.h"
33 #include "mozilla/InternalMutationEvent.h"
34 #include "mozilla/MathAlgorithms.h"
35 #include "mozilla/Maybe.h"
36 #include "mozilla/OwningNonNull.h"
37 #include "mozilla/Preferences.h"
38 #include "mozilla/PresShell.h"
39 #include "mozilla/RangeUtils.h"
40 #include "mozilla/StaticPrefs_editor.h" // for StaticPrefs::editor_*
41 #include "mozilla/TextComposition.h"
42 #include "mozilla/UniquePtr.h"
43 #include "mozilla/Unused.h"
44 #include "mozilla/dom/AncestorIterator.h"
45 #include "mozilla/dom/Element.h"
46 #include "mozilla/dom/HTMLBRElement.h"
47 #include "mozilla/dom/RangeBinding.h"
48 #include "mozilla/dom/Selection.h"
49 #include "mozilla/dom/StaticRange.h"
50 #include "mozilla/mozalloc.h"
51 #include "nsAString.h"
52 #include "nsAlgorithm.h"
53 #include "nsAtom.h"
54 #include "nsCRT.h"
55 #include "nsCRTGlue.h"
56 #include "nsComponentManagerUtils.h"
57 #include "nsContentUtils.h"
58 #include "nsDebug.h"
59 #include "nsError.h"
60 #include "nsFrameSelection.h"
61 #include "nsGkAtoms.h"
62 #include "nsHTMLDocument.h"
63 #include "nsIContent.h"
64 #include "nsID.h"
65 #include "nsIFrame.h"
66 #include "nsINode.h"
67 #include "nsLiteralString.h"
68 #include "nsPrintfCString.h"
69 #include "nsRange.h"
70 #include "nsReadableUtils.h"
71 #include "nsString.h"
72 #include "nsStringFwd.h"
73 #include "nsStyledElement.h"
74 #include "nsTArray.h"
75 #include "nsTextNode.h"
76 #include "nsThreadUtils.h"
77 #include "nsUnicharUtils.h"
79 class nsISupports;
81 namespace mozilla {
83 using namespace dom;
84 using EmptyCheckOption = HTMLEditUtils::EmptyCheckOption;
85 using EmptyCheckOptions = HTMLEditUtils::EmptyCheckOptions;
86 using LeafNodeType = HTMLEditUtils::LeafNodeType;
87 using LeafNodeTypes = HTMLEditUtils::LeafNodeTypes;
88 using WalkTextOption = HTMLEditUtils::WalkTextOption;
89 using WalkTreeDirection = HTMLEditUtils::WalkTreeDirection;
90 using WalkTreeOption = HTMLEditUtils::WalkTreeOption;
92 /********************************************************
93 * first some helpful functors we will use
94 ********************************************************/
96 static bool IsPendingStyleCachePreservingSubAction(
97 EditSubAction aEditSubAction) {
98 switch (aEditSubAction) {
99 case EditSubAction::eDeleteSelectedContent:
100 case EditSubAction::eInsertLineBreak:
101 case EditSubAction::eInsertParagraphSeparator:
102 case EditSubAction::eCreateOrChangeList:
103 case EditSubAction::eIndent:
104 case EditSubAction::eOutdent:
105 case EditSubAction::eSetOrClearAlignment:
106 case EditSubAction::eCreateOrRemoveBlock:
107 case EditSubAction::eFormatBlockForHTMLCommand:
108 case EditSubAction::eMergeBlockContents:
109 case EditSubAction::eRemoveList:
110 case EditSubAction::eCreateOrChangeDefinitionListItem:
111 case EditSubAction::eInsertElement:
112 case EditSubAction::eInsertQuotation:
113 case EditSubAction::eInsertQuotedText:
114 return true;
115 default:
116 return false;
120 template already_AddRefed<nsRange>
121 HTMLEditor::CreateRangeIncludingAdjuscentWhiteSpaces(
122 const EditorDOMRange& aRange);
123 template already_AddRefed<nsRange>
124 HTMLEditor::CreateRangeIncludingAdjuscentWhiteSpaces(
125 const EditorRawDOMRange& aRange);
126 template already_AddRefed<nsRange>
127 HTMLEditor::CreateRangeIncludingAdjuscentWhiteSpaces(
128 const EditorDOMPoint& aStartPoint, const EditorDOMPoint& aEndPoint);
129 template already_AddRefed<nsRange>
130 HTMLEditor::CreateRangeIncludingAdjuscentWhiteSpaces(
131 const EditorRawDOMPoint& aStartPoint, const EditorDOMPoint& aEndPoint);
132 template already_AddRefed<nsRange>
133 HTMLEditor::CreateRangeIncludingAdjuscentWhiteSpaces(
134 const EditorDOMPoint& aStartPoint, const EditorRawDOMPoint& aEndPoint);
135 template already_AddRefed<nsRange>
136 HTMLEditor::CreateRangeIncludingAdjuscentWhiteSpaces(
137 const EditorRawDOMPoint& aStartPoint, const EditorRawDOMPoint& aEndPoint);
139 nsresult HTMLEditor::InitEditorContentAndSelection() {
140 MOZ_ASSERT(IsEditActionDataAvailable());
142 // We should do nothing with the result of GetRoot() if only a part of the
143 // document is editable.
144 if (!EntireDocumentIsEditable()) {
145 return NS_OK;
148 nsresult rv = MaybeCreatePaddingBRElementForEmptyEditor();
149 if (NS_FAILED(rv)) {
150 NS_WARNING(
151 "HTMLEditor::MaybeCreatePaddingBRElementForEmptyEditor() failed");
152 return rv;
155 // If the selection hasn't been set up yet, set it up collapsed to the end of
156 // our editable content.
157 // XXX I think that this shouldn't do it in `HTMLEditor` because it maybe
158 // removed by the web app and if they call `Selection::AddRange()` without
159 // checking the range count, it may cause multiple selection ranges.
160 if (!SelectionRef().RangeCount()) {
161 nsresult rv = CollapseSelectionToEndOfLastLeafNodeOfDocument();
162 if (NS_FAILED(rv)) {
163 NS_WARNING(
164 "HTMLEditor::CollapseSelectionToEndOfLastLeafNodeOfDocument() "
165 "failed");
166 return rv;
170 if (IsPlaintextMailComposer()) {
171 // XXX Should we do this in HTMLEditor? It's odd to guarantee that last
172 // empty line is visible only when it's in the plain text mode.
173 nsresult rv = EnsurePaddingBRElementInMultilineEditor();
174 if (NS_FAILED(rv)) {
175 NS_WARNING(
176 "EditorBase::EnsurePaddingBRElementInMultilineEditor() failed");
177 return rv;
181 Element* bodyOrDocumentElement = GetRoot();
182 if (NS_WARN_IF(!bodyOrDocumentElement && !GetDocument())) {
183 return NS_ERROR_FAILURE;
186 if (!bodyOrDocumentElement) {
187 return NS_OK;
190 rv = InsertBRElementToEmptyListItemsAndTableCellsInRange(
191 RawRangeBoundary(bodyOrDocumentElement, 0u),
192 RawRangeBoundary(bodyOrDocumentElement,
193 bodyOrDocumentElement->GetChildCount()));
194 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
195 return NS_ERROR_EDITOR_DESTROYED;
197 NS_WARNING_ASSERTION(
198 NS_SUCCEEDED(rv),
199 "HTMLEditor::InsertBRElementToEmptyListItemsAndTableCellsInRange() "
200 "failed, but ignored");
201 return NS_OK;
204 void HTMLEditor::OnStartToHandleTopLevelEditSubAction(
205 EditSubAction aTopLevelEditSubAction,
206 nsIEditor::EDirection aDirectionOfTopLevelEditSubAction, ErrorResult& aRv) {
207 MOZ_ASSERT(IsEditActionDataAvailable());
208 MOZ_ASSERT(!aRv.Failed());
210 EditorBase::OnStartToHandleTopLevelEditSubAction(
211 aTopLevelEditSubAction, aDirectionOfTopLevelEditSubAction, aRv);
213 MOZ_ASSERT(GetTopLevelEditSubAction() == aTopLevelEditSubAction);
214 MOZ_ASSERT(GetDirectionOfTopLevelEditSubAction() ==
215 aDirectionOfTopLevelEditSubAction);
217 if (NS_WARN_IF(Destroyed())) {
218 aRv.Throw(NS_ERROR_EDITOR_DESTROYED);
219 return;
222 if (!mInitSucceeded) {
223 return; // We should do nothing if we're being initialized.
226 NS_WARNING_ASSERTION(
227 !aRv.Failed(),
228 "EditorBase::OnStartToHandleTopLevelEditSubAction() failed");
230 // Let's work with the latest layout information after (maybe) dispatching
231 // `beforeinput` event.
232 RefPtr<Document> document = GetDocument();
233 if (NS_WARN_IF(!document)) {
234 aRv.Throw(NS_ERROR_UNEXPECTED);
235 return;
237 document->FlushPendingNotifications(FlushType::Frames);
238 if (NS_WARN_IF(Destroyed())) {
239 aRv.Throw(NS_ERROR_EDITOR_DESTROYED);
240 return;
243 // Remember where our selection was before edit action took place:
244 const auto atCompositionStart =
245 GetFirstIMESelectionStartPoint<EditorRawDOMPoint>();
246 if (atCompositionStart.IsSet()) {
247 // If there is composition string, let's remember current composition
248 // range.
249 TopLevelEditSubActionDataRef().mSelectedRange->StoreRange(
250 atCompositionStart, GetLastIMESelectionEndPoint<EditorRawDOMPoint>());
251 } else {
252 // Get the selection location
253 // XXX This may occur so that I think that we shouldn't throw exception
254 // in this case.
255 if (NS_WARN_IF(!SelectionRef().RangeCount())) {
256 aRv.Throw(NS_ERROR_UNEXPECTED);
257 return;
259 if (const nsRange* range = SelectionRef().GetRangeAt(0)) {
260 TopLevelEditSubActionDataRef().mSelectedRange->StoreRange(*range);
264 // Register with range updater to track this as we perturb the doc
265 RangeUpdaterRef().RegisterRangeItem(
266 *TopLevelEditSubActionDataRef().mSelectedRange);
268 // Remember current inline styles for deletion and normal insertion ops
269 const bool cacheInlineStyles = [&]() {
270 switch (aTopLevelEditSubAction) {
271 case EditSubAction::eInsertText:
272 case EditSubAction::eInsertTextComingFromIME:
273 case EditSubAction::eDeleteSelectedContent:
274 return true;
275 default:
276 return IsPendingStyleCachePreservingSubAction(aTopLevelEditSubAction);
278 }();
279 if (cacheInlineStyles) {
280 const RefPtr<Element> editingHost =
281 ComputeEditingHost(LimitInBodyElement::No);
282 if (NS_WARN_IF(!editingHost)) {
283 aRv.Throw(NS_ERROR_FAILURE);
284 return;
287 nsIContent* const startContainer =
288 HTMLEditUtils::GetContentToPreserveInlineStyles(
289 TopLevelEditSubActionDataRef()
290 .mSelectedRange->StartPoint<EditorRawDOMPoint>(),
291 *editingHost);
292 if (NS_WARN_IF(!startContainer)) {
293 aRv.Throw(NS_ERROR_FAILURE);
294 return;
296 if (const RefPtr<Element> startContainerElement =
297 startContainer->GetAsElementOrParentElement()) {
298 nsresult rv = CacheInlineStyles(*startContainerElement);
299 if (NS_FAILED(rv)) {
300 NS_WARNING("HTMLEditor::CacheInlineStyles() failed");
301 aRv.Throw(rv);
302 return;
307 // Stabilize the document against contenteditable count changes
308 if (document->GetEditingState() == Document::EditingState::eContentEditable) {
309 document->ChangeContentEditableCount(nullptr, +1);
310 TopLevelEditSubActionDataRef().mRestoreContentEditableCount = true;
313 // Check that selection is in subtree defined by body node
314 nsresult rv = EnsureSelectionInBodyOrDocumentElement();
315 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
316 aRv.Throw(NS_ERROR_EDITOR_DESTROYED);
317 return;
319 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
320 "HTMLEditor::EnsureSelectionInBodyOrDocumentElement() "
321 "failed, but ignored");
324 nsresult HTMLEditor::OnEndHandlingTopLevelEditSubAction() {
325 MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable());
327 nsresult rv;
328 while (true) {
329 if (NS_WARN_IF(Destroyed())) {
330 rv = NS_ERROR_EDITOR_DESTROYED;
331 break;
334 if (!mInitSucceeded) {
335 rv = NS_OK; // We should do nothing if we're being initialized.
336 break;
339 // Do all the tricky stuff
340 rv = OnEndHandlingTopLevelEditSubActionInternal();
341 NS_WARNING_ASSERTION(
342 NS_SUCCEEDED(rv),
343 "HTMLEditor::OnEndHandlingTopLevelEditSubActionInternal() failied");
344 // Perhaps, we need to do the following jobs even if the editor has been
345 // destroyed since they adjust some states of HTML document but don't
346 // modify the DOM tree nor Selection.
348 // Free up selectionState range item
349 if (TopLevelEditSubActionDataRef().mSelectedRange) {
350 RangeUpdaterRef().DropRangeItem(
351 *TopLevelEditSubActionDataRef().mSelectedRange);
354 // Reset the contenteditable count to its previous value
355 if (TopLevelEditSubActionDataRef().mRestoreContentEditableCount) {
356 Document* document = GetDocument();
357 if (NS_WARN_IF(!document)) {
358 rv = NS_ERROR_FAILURE;
359 break;
361 if (document->GetEditingState() ==
362 Document::EditingState::eContentEditable) {
363 document->ChangeContentEditableCount(nullptr, -1);
366 break;
368 DebugOnly<nsresult> rvIgnored =
369 EditorBase::OnEndHandlingTopLevelEditSubAction();
370 NS_WARNING_ASSERTION(
371 NS_FAILED(rv) || NS_SUCCEEDED(rvIgnored),
372 "EditorBase::OnEndHandlingTopLevelEditSubAction() failed, but ignored");
373 MOZ_ASSERT(!GetTopLevelEditSubAction());
374 MOZ_ASSERT(GetDirectionOfTopLevelEditSubAction() == eNone);
375 return rv;
378 nsresult HTMLEditor::OnEndHandlingTopLevelEditSubActionInternal() {
379 MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable());
381 nsresult rv = EnsureSelectionInBodyOrDocumentElement();
382 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
383 return NS_ERROR_EDITOR_DESTROYED;
385 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
386 "HTMLEditor::EnsureSelectionInBodyOrDocumentElement() "
387 "failed, but ignored");
389 switch (GetTopLevelEditSubAction()) {
390 case EditSubAction::eReplaceHeadWithHTMLSource:
391 case EditSubAction::eCreatePaddingBRElementForEmptyEditor:
392 return NS_OK;
393 default:
394 break;
397 if (TopLevelEditSubActionDataRef().mChangedRange->IsPositioned() &&
398 GetTopLevelEditSubAction() != EditSubAction::eUndo &&
399 GetTopLevelEditSubAction() != EditSubAction::eRedo) {
400 // don't let any txns in here move the selection around behind our back.
401 // Note that this won't prevent explicit selection setting from working.
402 AutoTransactionsConserveSelection dontChangeMySelection(*this);
405 EditorDOMRange changedRange(
406 *TopLevelEditSubActionDataRef().mChangedRange);
407 if (changedRange.IsPositioned() &&
408 changedRange.EnsureNotInNativeAnonymousSubtree()) {
409 bool isBlockLevelSubAction = false;
410 switch (GetTopLevelEditSubAction()) {
411 case EditSubAction::eInsertText:
412 case EditSubAction::eInsertTextComingFromIME:
413 case EditSubAction::eInsertLineBreak:
414 case EditSubAction::eInsertParagraphSeparator:
415 case EditSubAction::eDeleteText: {
416 // XXX We should investigate whether this is really needed because
417 // it seems that the following code does not handle the
418 // white-spaces.
419 RefPtr<nsRange> extendedChangedRange =
420 CreateRangeIncludingAdjuscentWhiteSpaces(changedRange);
421 if (extendedChangedRange) {
422 MOZ_ASSERT(extendedChangedRange->IsPositioned());
423 // Use extended range temporarily.
424 TopLevelEditSubActionDataRef().mChangedRange =
425 std::move(extendedChangedRange);
427 break;
429 case EditSubAction::eCreateOrChangeList:
430 case EditSubAction::eCreateOrChangeDefinitionListItem:
431 case EditSubAction::eRemoveList:
432 case EditSubAction::eFormatBlockForHTMLCommand:
433 case EditSubAction::eCreateOrRemoveBlock:
434 case EditSubAction::eIndent:
435 case EditSubAction::eOutdent:
436 case EditSubAction::eSetOrClearAlignment:
437 case EditSubAction::eSetPositionToAbsolute:
438 case EditSubAction::eSetPositionToStatic:
439 case EditSubAction::eDecreaseZIndex:
440 case EditSubAction::eIncreaseZIndex:
441 isBlockLevelSubAction = true;
442 [[fallthrough]];
443 default: {
444 Element* editingHost = ComputeEditingHost();
445 if (MOZ_UNLIKELY(!editingHost)) {
446 break;
448 RefPtr<nsRange> extendedChangedRange = AutoRangeArray::
449 CreateRangeWrappingStartAndEndLinesContainingBoundaries(
450 changedRange, GetTopLevelEditSubAction(),
451 isBlockLevelSubAction
452 ? BlockInlineCheck::UseHTMLDefaultStyle
453 : BlockInlineCheck::UseComputedDisplayOutsideStyle,
454 *editingHost);
455 if (!extendedChangedRange) {
456 break;
458 MOZ_ASSERT(extendedChangedRange->IsPositioned());
459 // Use extended range temporarily.
460 TopLevelEditSubActionDataRef().mChangedRange =
461 std::move(extendedChangedRange);
462 break;
468 // if we did a ranged deletion or handling backspace key, make sure we have
469 // a place to put caret.
470 // Note we only want to do this if the overall operation was deletion,
471 // not if deletion was done along the way for
472 // EditSubAction::eInsertHTMLSource, EditSubAction::eInsertText, etc.
473 // That's why this is here rather than DeleteSelectionAsSubAction().
474 // However, we shouldn't insert <br> elements if we've already removed
475 // empty block parents because users may want to disappear the line by
476 // the deletion.
477 // XXX We should make HandleDeleteSelection() store expected container
478 // for handling this here since we cannot trust current selection is
479 // collapsed at deleted point.
480 if (GetTopLevelEditSubAction() == EditSubAction::eDeleteSelectedContent &&
481 TopLevelEditSubActionDataRef().mDidDeleteNonCollapsedRange &&
482 !TopLevelEditSubActionDataRef().mDidDeleteEmptyParentBlocks) {
483 const auto newCaretPosition =
484 GetFirstSelectionStartPoint<EditorDOMPoint>();
485 if (!newCaretPosition.IsSet()) {
486 NS_WARNING("There was no selection range");
487 return NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE;
489 Result<CaretPoint, nsresult> caretPointOrError =
490 InsertBRElementIfHardLineIsEmptyAndEndsWithBlockBoundary(
491 newCaretPosition);
492 if (MOZ_UNLIKELY(caretPointOrError.isErr())) {
493 NS_WARNING(
494 "HTMLEditor::"
495 "InsertBRElementIfHardLineIsEmptyAndEndsWithBlockBoundary() "
496 "failed");
497 return caretPointOrError.unwrapErr();
499 nsresult rv = caretPointOrError.unwrap().SuggestCaretPointTo(
500 *this, {SuggestCaret::OnlyIfHasSuggestion});
501 if (NS_FAILED(rv)) {
502 NS_WARNING("CaretPoint::SuggestCaretPointTo() failed");
503 return rv;
505 NS_WARNING_ASSERTION(
506 rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
507 "CaretPoint::SuggestCaretPointTo() failed, but ignored");
510 // add in any needed <br>s, and remove any unneeded ones.
511 nsresult rv = InsertBRElementToEmptyListItemsAndTableCellsInRange(
512 TopLevelEditSubActionDataRef().mChangedRange->StartRef().AsRaw(),
513 TopLevelEditSubActionDataRef().mChangedRange->EndRef().AsRaw());
514 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
515 return NS_ERROR_EDITOR_DESTROYED;
517 NS_WARNING_ASSERTION(
518 NS_SUCCEEDED(rv),
519 "HTMLEditor::InsertBRElementToEmptyListItemsAndTableCellsInRange()"
520 " failed, but ignored");
522 // merge any adjacent text nodes
523 switch (GetTopLevelEditSubAction()) {
524 case EditSubAction::eInsertText:
525 case EditSubAction::eInsertTextComingFromIME:
526 break;
527 default: {
528 nsresult rv = CollapseAdjacentTextNodes(
529 MOZ_KnownLive(*TopLevelEditSubActionDataRef().mChangedRange));
530 if (NS_WARN_IF(Destroyed())) {
531 return NS_ERROR_EDITOR_DESTROYED;
533 if (NS_FAILED(rv)) {
534 NS_WARNING("HTMLEditor::CollapseAdjacentTextNodes() failed");
535 return rv;
537 break;
541 // Clean up any empty nodes in the changed range unless they are inserted
542 // intentionally.
543 if (TopLevelEditSubActionDataRef().mNeedsToCleanUpEmptyElements) {
544 nsresult rv = RemoveEmptyNodesIn(
545 EditorDOMRange(*TopLevelEditSubActionDataRef().mChangedRange));
546 if (NS_FAILED(rv)) {
547 NS_WARNING("HTMLEditor::RemoveEmptyNodesIn() failed");
548 return rv;
552 // attempt to transform any unneeded nbsp's into spaces after doing various
553 // operations
554 switch (GetTopLevelEditSubAction()) {
555 case EditSubAction::eDeleteSelectedContent:
556 if (TopLevelEditSubActionDataRef().mDidNormalizeWhitespaces) {
557 break;
559 [[fallthrough]];
560 case EditSubAction::eInsertText:
561 case EditSubAction::eInsertTextComingFromIME:
562 case EditSubAction::eInsertLineBreak:
563 case EditSubAction::eInsertParagraphSeparator:
564 case EditSubAction::ePasteHTMLContent:
565 case EditSubAction::eInsertHTMLSource: {
566 // Due to the replacement of white-spaces in
567 // WhiteSpaceVisibilityKeeper::NormalizeVisibleWhiteSpacesAt(),
568 // selection ranges may be changed since DOM ranges track the DOM
569 // mutation by themselves. However, we want to keep selection as-is.
570 // Therefore, we should restore `Selection` after replacing
571 // white-spaces.
572 AutoSelectionRestorer restoreSelection(this);
573 // TODO: Temporarily, WhiteSpaceVisibilityKeeper replaces ASCII
574 // white-spaces with NPSPs and then, we'll replace them with ASCII
575 // white-spaces here. We should avoid this overwriting things as
576 // far as possible because replacing characters in text nodes
577 // causes running mutation event listeners which are really
578 // expensive.
579 // Adjust end of composition string if there is composition string.
580 auto pointToAdjust = GetLastIMESelectionEndPoint<EditorDOMPoint>();
581 if (!pointToAdjust.IsInContentNode()) {
582 // Otherwise, adjust current selection start point.
583 pointToAdjust = GetFirstSelectionStartPoint<EditorDOMPoint>();
584 if (NS_WARN_IF(!pointToAdjust.IsInContentNode())) {
585 return NS_ERROR_FAILURE;
588 if (EditorUtils::IsEditableContent(
589 *pointToAdjust.ContainerAs<nsIContent>(), EditorType::HTML)) {
590 AutoTrackDOMPoint trackPointToAdjust(RangeUpdaterRef(),
591 &pointToAdjust);
592 nsresult rv =
593 WhiteSpaceVisibilityKeeper::NormalizeVisibleWhiteSpacesAt(
594 *this, pointToAdjust);
595 if (NS_FAILED(rv)) {
596 NS_WARNING(
597 "WhiteSpaceVisibilityKeeper::NormalizeVisibleWhiteSpacesAt() "
598 "failed");
599 return rv;
603 // also do this for original selection endpoints.
604 // XXX Hmm, if `NormalizeVisibleWhiteSpacesAt()` runs mutation event
605 // listener and that causes changing `mSelectedRange`, what we
606 // should do?
607 if (NS_WARN_IF(!TopLevelEditSubActionDataRef()
608 .mSelectedRange->IsPositioned())) {
609 return NS_ERROR_FAILURE;
612 EditorDOMPoint atStart =
613 TopLevelEditSubActionDataRef().mSelectedRange->StartPoint();
614 if (atStart != pointToAdjust && atStart.IsInContentNode() &&
615 EditorUtils::IsEditableContent(*atStart.ContainerAs<nsIContent>(),
616 EditorType::HTML)) {
617 AutoTrackDOMPoint trackPointToAdjust(RangeUpdaterRef(),
618 &pointToAdjust);
619 AutoTrackDOMPoint trackStartPoint(RangeUpdaterRef(), &atStart);
620 nsresult rv =
621 WhiteSpaceVisibilityKeeper::NormalizeVisibleWhiteSpacesAt(
622 *this, atStart);
623 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
624 return NS_ERROR_EDITOR_DESTROYED;
626 NS_WARNING_ASSERTION(
627 NS_SUCCEEDED(rv),
628 "WhiteSpaceVisibilityKeeper::NormalizeVisibleWhiteSpacesAt() "
629 "failed, but ignored");
631 // we only need to handle old selection endpoint if it was different
632 // from start
633 EditorDOMPoint atEnd =
634 TopLevelEditSubActionDataRef().mSelectedRange->EndPoint();
635 if (!TopLevelEditSubActionDataRef().mSelectedRange->Collapsed() &&
636 atEnd != pointToAdjust && atEnd != atStart &&
637 atEnd.IsInContentNode() &&
638 EditorUtils::IsEditableContent(*atEnd.ContainerAs<nsIContent>(),
639 EditorType::HTML)) {
640 nsresult rv =
641 WhiteSpaceVisibilityKeeper::NormalizeVisibleWhiteSpacesAt(*this,
642 atEnd);
643 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
644 return NS_ERROR_EDITOR_DESTROYED;
646 NS_WARNING_ASSERTION(
647 NS_SUCCEEDED(rv),
648 "WhiteSpaceVisibilityKeeper::NormalizeVisibleWhiteSpacesAt() "
649 "failed, but ignored");
651 break;
653 default:
654 break;
657 // Adjust selection for insert text, html paste, and delete actions if
658 // we haven't removed new empty blocks. Note that if empty block parents
659 // are removed, Selection should've been adjusted by the method which
660 // did it.
661 if (!TopLevelEditSubActionDataRef().mDidDeleteEmptyParentBlocks &&
662 SelectionRef().IsCollapsed()) {
663 switch (GetTopLevelEditSubAction()) {
664 case EditSubAction::eInsertText:
665 case EditSubAction::eInsertTextComingFromIME:
666 case EditSubAction::eDeleteSelectedContent:
667 case EditSubAction::eInsertLineBreak:
668 case EditSubAction::eInsertParagraphSeparator:
669 case EditSubAction::ePasteHTMLContent:
670 case EditSubAction::eInsertHTMLSource:
671 // XXX AdjustCaretPositionAndEnsurePaddingBRElement() intentionally
672 // does not create padding `<br>` element for empty editor.
673 // Investigate which is better that whether this should does it
674 // or wait MaybeCreatePaddingBRElementForEmptyEditor().
675 rv = AdjustCaretPositionAndEnsurePaddingBRElement(
676 GetDirectionOfTopLevelEditSubAction());
677 if (NS_FAILED(rv)) {
678 NS_WARNING(
679 "HTMLEditor::AdjustCaretPositionAndEnsurePaddingBRElement() "
680 "failed");
681 return rv;
683 break;
684 default:
685 break;
689 // check for any styles which were removed inappropriately
690 bool reapplyCachedStyle;
691 switch (GetTopLevelEditSubAction()) {
692 case EditSubAction::eInsertText:
693 case EditSubAction::eInsertTextComingFromIME:
694 case EditSubAction::eDeleteSelectedContent:
695 reapplyCachedStyle = true;
696 break;
697 default:
698 reapplyCachedStyle =
699 IsPendingStyleCachePreservingSubAction(GetTopLevelEditSubAction());
700 break;
703 // If the selection is in empty inline HTML elements, we should delete
704 // them unless it's inserted intentionally.
705 if (mPlaceholderBatch &&
706 TopLevelEditSubActionDataRef().mNeedsToCleanUpEmptyElements &&
707 SelectionRef().IsCollapsed() && SelectionRef().GetFocusNode()) {
708 RefPtr<Element> mostDistantEmptyInlineAncestor = nullptr;
709 for (Element* ancestor :
710 SelectionRef().GetFocusNode()->InclusiveAncestorsOfType<Element>()) {
711 if (!ancestor->IsHTMLElement() ||
712 !HTMLEditUtils::IsRemovableFromParentNode(*ancestor) ||
713 !HTMLEditUtils::IsEmptyInlineContainer(
714 *ancestor, {EmptyCheckOption::TreatSingleBRElementAsVisible},
715 BlockInlineCheck::UseComputedDisplayStyle)) {
716 break;
718 mostDistantEmptyInlineAncestor = ancestor;
720 if (mostDistantEmptyInlineAncestor) {
721 nsresult rv =
722 DeleteNodeWithTransaction(*mostDistantEmptyInlineAncestor);
723 if (NS_FAILED(rv)) {
724 NS_WARNING(
725 "EditorBase::DeleteNodeWithTransaction() failed at deleting "
726 "empty inline ancestors");
727 return rv;
732 // But the cached inline styles should be restored from type-in-state later.
733 if (reapplyCachedStyle) {
734 DebugOnly<nsresult> rvIgnored =
735 mPendingStylesToApplyToNewContent->UpdateSelState(*this);
736 NS_WARNING_ASSERTION(
737 NS_SUCCEEDED(rvIgnored),
738 "PendingStyles::UpdateSelState() failed, but ignored");
739 rvIgnored = ReapplyCachedStyles();
740 NS_WARNING_ASSERTION(
741 NS_SUCCEEDED(rvIgnored),
742 "HTMLEditor::ReapplyCachedStyles() failed, but ignored");
743 TopLevelEditSubActionDataRef().mCachedPendingStyles->Clear();
747 rv = HandleInlineSpellCheck(
748 TopLevelEditSubActionDataRef().mSelectedRange->StartPoint(),
749 TopLevelEditSubActionDataRef().mChangedRange);
750 if (NS_FAILED(rv)) {
751 NS_WARNING("EditorBase::HandleInlineSpellCheck() failed");
752 return rv;
755 // detect empty doc
756 // XXX Need to investigate when the padding <br> element is removed because
757 // I don't see the <br> element with testing manually. If it won't be
758 // used, we can get rid of this cost.
759 rv = MaybeCreatePaddingBRElementForEmptyEditor();
760 if (NS_FAILED(rv)) {
761 NS_WARNING(
762 "EditorBase::MaybeCreatePaddingBRElementForEmptyEditor() failed");
763 return rv;
766 // adjust selection HINT if needed
767 if (!TopLevelEditSubActionDataRef().mDidExplicitlySetInterLine &&
768 SelectionRef().IsCollapsed()) {
769 SetSelectionInterlinePosition();
772 return NS_OK;
775 Result<EditActionResult, nsresult> HTMLEditor::CanHandleHTMLEditSubAction(
776 CheckSelectionInReplacedElement aCheckSelectionInReplacedElement
777 /* = CheckSelectionInReplacedElement::Yes */) const {
778 MOZ_ASSERT(IsEditActionDataAvailable());
780 if (NS_WARN_IF(Destroyed())) {
781 return Err(NS_ERROR_EDITOR_DESTROYED);
784 // If there is not selection ranges, we should ignore the result.
785 if (!SelectionRef().RangeCount()) {
786 return EditActionResult::CanceledResult();
789 const nsRange* range = SelectionRef().GetRangeAt(0);
790 nsINode* selStartNode = range->GetStartContainer();
791 if (NS_WARN_IF(!selStartNode) || NS_WARN_IF(!selStartNode->IsContent())) {
792 return Err(NS_ERROR_FAILURE);
795 if (!HTMLEditUtils::IsSimplyEditableNode(*selStartNode)) {
796 return EditActionResult::CanceledResult();
799 nsINode* selEndNode = range->GetEndContainer();
800 if (NS_WARN_IF(!selEndNode) || NS_WARN_IF(!selEndNode->IsContent())) {
801 return Err(NS_ERROR_FAILURE);
804 if (selStartNode == selEndNode) {
805 if (aCheckSelectionInReplacedElement ==
806 CheckSelectionInReplacedElement::Yes &&
807 HTMLEditUtils::IsNonEditableReplacedContent(
808 *selStartNode->AsContent())) {
809 return EditActionResult::CanceledResult();
811 return EditActionResult::IgnoredResult();
814 if (HTMLEditUtils::IsNonEditableReplacedContent(*selStartNode->AsContent()) ||
815 HTMLEditUtils::IsNonEditableReplacedContent(*selEndNode->AsContent())) {
816 return EditActionResult::CanceledResult();
819 if (!HTMLEditUtils::IsSimplyEditableNode(*selEndNode)) {
820 return EditActionResult::CanceledResult();
823 // If anchor node is in an HTML element which has inert attribute, we should
824 // do nothing.
825 // XXX HTMLEditor typically uses first range instead of anchor/focus range.
826 // Therefore, referring first range here is more reasonable than
827 // anchor/focus range of Selection.
828 nsIContent* const selAnchorContent = SelectionRef().GetDirection() == eDirNext
829 ? nsIContent::FromNode(selStartNode)
830 : nsIContent::FromNode(selEndNode);
831 if (selAnchorContent &&
832 HTMLEditUtils::ContentIsInert(*selAnchorContent->AsContent())) {
833 return EditActionResult::CanceledResult();
836 // XXX What does it mean the common ancestor is editable? I have no idea.
837 // It should be in same (active) editing host, and even if it's editable,
838 // there may be non-editable contents in the range.
839 nsINode* commonAncestor = range->GetClosestCommonInclusiveAncestor();
840 if (MOZ_UNLIKELY(!commonAncestor)) {
841 NS_WARNING(
842 "AbstractRange::GetClosestCommonInclusiveAncestor() returned nullptr");
843 return Err(NS_ERROR_FAILURE);
845 return HTMLEditUtils::IsSimplyEditableNode(*commonAncestor)
846 ? EditActionResult::IgnoredResult()
847 : EditActionResult::CanceledResult();
850 MOZ_CAN_RUN_SCRIPT static nsStaticAtom& MarginPropertyAtomForIndent(
851 nsIContent& aContent) {
852 nsAutoString direction;
853 DebugOnly<nsresult> rvIgnored = CSSEditUtils::GetComputedProperty(
854 aContent, *nsGkAtoms::direction, direction);
855 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
856 "CSSEditUtils::GetComputedProperty(nsGkAtoms::direction)"
857 " failed, but ignored");
858 return direction.EqualsLiteral("rtl") ? *nsGkAtoms::marginRight
859 : *nsGkAtoms::marginLeft;
862 nsresult HTMLEditor::EnsureCaretNotAfterInvisibleBRElement() {
863 MOZ_ASSERT(IsEditActionDataAvailable());
864 MOZ_ASSERT(SelectionRef().IsCollapsed());
866 // If we are after a padding `<br>` element for empty last line in the same
867 // block, then move selection to be before it
868 const nsRange* firstRange = SelectionRef().GetRangeAt(0);
869 if (NS_WARN_IF(!firstRange)) {
870 return NS_ERROR_FAILURE;
873 EditorRawDOMPoint atSelectionStart(firstRange->StartRef());
874 if (NS_WARN_IF(!atSelectionStart.IsSet())) {
875 return NS_ERROR_FAILURE;
877 MOZ_ASSERT(atSelectionStart.IsSetAndValid());
879 if (!atSelectionStart.IsInContentNode()) {
880 return NS_OK;
883 Element* editingHost = ComputeEditingHost();
884 if (!editingHost) {
885 NS_WARNING(
886 "HTMLEditor::EnsureCaretNotAfterInvisibleBRElement() did nothing "
887 "because of no editing host");
888 return NS_OK;
891 nsIContent* previousBRElement = HTMLEditUtils::GetPreviousContent(
892 atSelectionStart, {}, BlockInlineCheck::UseComputedDisplayStyle,
893 editingHost);
894 if (!previousBRElement || !previousBRElement->IsHTMLElement(nsGkAtoms::br) ||
895 !previousBRElement->GetParent() ||
896 !EditorUtils::IsEditableContent(*previousBRElement->GetParent(),
897 EditorType::HTML) ||
898 !HTMLEditUtils::IsInvisibleBRElement(*previousBRElement)) {
899 return NS_OK;
902 const RefPtr<const Element> blockElementAtSelectionStart =
903 HTMLEditUtils::GetInclusiveAncestorElement(
904 *atSelectionStart.ContainerAs<nsIContent>(),
905 HTMLEditUtils::ClosestBlockElement,
906 BlockInlineCheck::UseComputedDisplayStyle);
907 const RefPtr<const Element> parentBlockElementOfBRElement =
908 HTMLEditUtils::GetAncestorElement(
909 *previousBRElement, HTMLEditUtils::ClosestBlockElement,
910 BlockInlineCheck::UseComputedDisplayStyle);
912 if (!blockElementAtSelectionStart ||
913 blockElementAtSelectionStart != parentBlockElementOfBRElement) {
914 return NS_OK;
917 // If we are here then the selection is right after a padding <br>
918 // element for empty last line that is in the same block as the
919 // selection. We need to move the selection start to be before the
920 // padding <br> element.
921 EditorRawDOMPoint atInvisibleBRElement(previousBRElement);
922 nsresult rv = CollapseSelectionTo(atInvisibleBRElement);
923 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
924 "EditorBase::CollapseSelectionTo() failed");
925 return rv;
928 nsresult HTMLEditor::MaybeCreatePaddingBRElementForEmptyEditor() {
929 MOZ_ASSERT(IsEditActionDataAvailable());
931 if (mPaddingBRElementForEmptyEditor) {
932 return NS_OK;
935 // XXX I think that we should not insert a <br> element if we're for a web
936 // content. Probably, this is required only by chrome editors such as
937 // the mail composer of Thunderbird and the composer of SeaMonkey.
939 const RefPtr<Element> bodyOrDocumentElement = GetRoot();
940 if (!bodyOrDocumentElement) {
941 return NS_OK;
944 // Skip adding the padding <br> element for empty editor if body
945 // is read-only.
946 if (!HTMLEditUtils::IsSimplyEditableNode(*bodyOrDocumentElement)) {
947 return NS_OK;
950 // Now we've got the body element. Iterate over the body element's children,
951 // looking for editable content. If no editable content is found, insert the
952 // padding <br> element.
953 EditorType editorType = GetEditorType();
954 bool isRootEditable =
955 EditorUtils::IsEditableContent(*bodyOrDocumentElement, editorType);
956 for (nsIContent* child = bodyOrDocumentElement->GetFirstChild(); child;
957 child = child->GetNextSibling()) {
958 if (EditorUtils::IsPaddingBRElementForEmptyEditor(*child) ||
959 !isRootEditable || EditorUtils::IsEditableContent(*child, editorType) ||
960 HTMLEditUtils::IsBlockElement(
961 *child, BlockInlineCheck::UseComputedDisplayStyle)) {
962 return NS_OK;
966 IgnoredErrorResult ignoredError;
967 AutoEditSubActionNotifier startToHandleEditSubAction(
968 *this, EditSubAction::eCreatePaddingBRElementForEmptyEditor,
969 nsIEditor::eNone, ignoredError);
970 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
971 return ignoredError.StealNSResult();
973 NS_WARNING_ASSERTION(
974 !ignoredError.Failed(),
975 "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
977 // Create a br.
978 RefPtr<HTMLBRElement> newBRElement =
979 HTMLBRElement::FromNodeOrNull(RefPtr{CreateHTMLContent(nsGkAtoms::br)});
980 if (NS_WARN_IF(Destroyed())) {
981 return NS_ERROR_EDITOR_DESTROYED;
983 if (NS_WARN_IF(!newBRElement)) {
984 return NS_ERROR_FAILURE;
987 mPaddingBRElementForEmptyEditor = newBRElement;
989 // Give it a special attribute.
990 nsresult rv =
991 UpdateBRElementType(*newBRElement, BRElementType::PaddingForEmptyEditor);
992 if (NS_FAILED(rv)) {
993 NS_WARNING("EditorBase::UpdateBRElementType() failed");
994 return rv;
997 // Put the node in the document.
998 Result<CreateElementResult, nsresult> insertBRElementResult =
999 InsertNodeWithTransaction<Element>(
1000 *newBRElement, EditorDOMPoint(bodyOrDocumentElement, 0u));
1001 if (MOZ_UNLIKELY(insertBRElementResult.isErr())) {
1002 NS_WARNING("EditorBase::InsertNodeWithTransaction() failed");
1003 return insertBRElementResult.unwrapErr();
1006 // Set selection.
1007 insertBRElementResult.inspect().IgnoreCaretPointSuggestion();
1008 rv = CollapseSelectionToStartOf(*bodyOrDocumentElement);
1009 if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) {
1010 NS_WARNING(
1011 "EditorBase::CollapseSelectionToStartOf() caused destroying the "
1012 "editor");
1013 return NS_ERROR_EDITOR_DESTROYED;
1015 NS_WARNING_ASSERTION(
1016 NS_SUCCEEDED(rv),
1017 "EditorBase::CollapseSelectionToStartOf() failed, but ignored");
1018 return NS_OK;
1021 nsresult HTMLEditor::EnsureNoPaddingBRElementForEmptyEditor() {
1022 MOZ_ASSERT(IsEditActionDataAvailable());
1024 if (!mPaddingBRElementForEmptyEditor) {
1025 return NS_OK;
1028 // If we're an HTML editor, a mutation event listener may recreate padding
1029 // <br> element for empty editor again during the call of
1030 // DeleteNodeWithTransaction(). So, move it first.
1031 RefPtr<HTMLBRElement> paddingBRElement(
1032 std::move(mPaddingBRElementForEmptyEditor));
1033 nsresult rv = DeleteNodeWithTransaction(*paddingBRElement);
1034 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
1035 "EditorBase::DeleteNodeWithTransaction() failed");
1036 return rv;
1039 nsresult HTMLEditor::ReflectPaddingBRElementForEmptyEditor() {
1040 if (NS_WARN_IF(!mRootElement)) {
1041 NS_WARNING("Failed to handle padding BR element due to no root element");
1042 return NS_ERROR_FAILURE;
1044 // The idea here is to see if the magic empty node has suddenly reappeared. If
1045 // it has, set our state so we remember it. There is a tradeoff between doing
1046 // here and at redo, or doing it everywhere else that might care. Since undo
1047 // and redo are relatively rare, it makes sense to take the (small)
1048 // performance hit here.
1049 nsIContent* firstLeafChild = HTMLEditUtils::GetFirstLeafContent(
1050 *mRootElement, {LeafNodeType::OnlyLeafNode});
1051 if (firstLeafChild &&
1052 EditorUtils::IsPaddingBRElementForEmptyEditor(*firstLeafChild)) {
1053 mPaddingBRElementForEmptyEditor =
1054 static_cast<HTMLBRElement*>(firstLeafChild);
1055 } else {
1056 mPaddingBRElementForEmptyEditor = nullptr;
1058 return NS_OK;
1061 nsresult HTMLEditor::PrepareInlineStylesForCaret() {
1062 MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable());
1063 MOZ_ASSERT(SelectionRef().IsCollapsed());
1065 // XXX This method works with the top level edit sub-action, but this
1066 // must be wrong if we are handling nested edit action.
1068 if (TopLevelEditSubActionDataRef().mDidDeleteSelection) {
1069 switch (GetTopLevelEditSubAction()) {
1070 case EditSubAction::eInsertText:
1071 case EditSubAction::eInsertTextComingFromIME:
1072 case EditSubAction::eDeleteSelectedContent: {
1073 nsresult rv = ReapplyCachedStyles();
1074 if (NS_FAILED(rv)) {
1075 NS_WARNING("HTMLEditor::ReapplyCachedStyles() failed");
1076 return rv;
1078 break;
1080 default:
1081 break;
1084 // For most actions we want to clear the cached styles, but there are
1085 // exceptions
1086 if (!IsPendingStyleCachePreservingSubAction(GetTopLevelEditSubAction())) {
1087 TopLevelEditSubActionDataRef().mCachedPendingStyles->Clear();
1089 return NS_OK;
1092 Result<EditActionResult, nsresult> HTMLEditor::HandleInsertText(
1093 EditSubAction aEditSubAction, const nsAString& aInsertionString,
1094 SelectionHandling aSelectionHandling) {
1095 MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable());
1096 MOZ_ASSERT(aEditSubAction == EditSubAction::eInsertText ||
1097 aEditSubAction == EditSubAction::eInsertTextComingFromIME);
1098 MOZ_ASSERT_IF(aSelectionHandling == SelectionHandling::Ignore,
1099 aEditSubAction == EditSubAction::eInsertTextComingFromIME);
1102 Result<EditActionResult, nsresult> result = CanHandleHTMLEditSubAction();
1103 if (MOZ_UNLIKELY(result.isErr())) {
1104 NS_WARNING("HTMLEditor::CanHandleHTMLEditSubAction() failed");
1105 return result;
1107 if (result.inspect().Canceled()) {
1108 return result;
1112 UndefineCaretBidiLevel();
1114 // If the selection isn't collapsed, delete it. Don't delete existing inline
1115 // tags, because we're hopefully going to insert text (bug 787432).
1116 if (!SelectionRef().IsCollapsed() &&
1117 aSelectionHandling == SelectionHandling::Delete) {
1118 nsresult rv =
1119 DeleteSelectionAsSubAction(nsIEditor::eNone, nsIEditor::eNoStrip);
1120 if (NS_FAILED(rv)) {
1121 NS_WARNING(
1122 "EditorBase::DeleteSelectionAsSubAction(nsIEditor::eNone, "
1123 "nsIEditor::eNoStrip) failed");
1124 return Err(rv);
1128 nsresult rv = EnsureNoPaddingBRElementForEmptyEditor();
1129 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
1130 return Err(NS_ERROR_EDITOR_DESTROYED);
1132 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
1133 "EditorBase::EnsureNoPaddingBRElementForEmptyEditor() "
1134 "failed, but ignored");
1136 if (NS_SUCCEEDED(rv) && SelectionRef().IsCollapsed()) {
1137 nsresult rv = EnsureCaretNotAfterInvisibleBRElement();
1138 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
1139 return Err(NS_ERROR_EDITOR_DESTROYED);
1141 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
1142 "HTMLEditor::EnsureCaretNotAfterInvisibleBRElement() "
1143 "failed, but ignored");
1144 if (NS_SUCCEEDED(rv)) {
1145 nsresult rv = PrepareInlineStylesForCaret();
1146 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
1147 return Err(NS_ERROR_EDITOR_DESTROYED);
1149 NS_WARNING_ASSERTION(
1150 NS_SUCCEEDED(rv),
1151 "HTMLEditor::PrepareInlineStylesForCaret() failed, but ignored");
1155 RefPtr<Document> document = GetDocument();
1156 if (NS_WARN_IF(!document)) {
1157 return Err(NS_ERROR_FAILURE);
1160 const RefPtr<Element> editingHost = ComputeEditingHost(
1161 GetDocument()->IsXMLDocument() ? LimitInBodyElement::No
1162 : LimitInBodyElement::Yes);
1163 if (NS_WARN_IF(!editingHost)) {
1164 return Err(NS_ERROR_FAILURE);
1167 auto pointToInsert = GetFirstSelectionStartPoint<EditorDOMPoint>();
1168 if (MOZ_UNLIKELY(!pointToInsert.IsSet())) {
1169 return Err(NS_ERROR_FAILURE);
1172 // for every property that is set, insert a new inline style node
1173 Result<EditorDOMPoint, nsresult> setStyleResult =
1174 CreateStyleForInsertText(pointToInsert, *editingHost);
1175 if (MOZ_UNLIKELY(setStyleResult.isErr())) {
1176 NS_WARNING("HTMLEditor::CreateStyleForInsertText() failed");
1177 return setStyleResult.propagateErr();
1179 if (setStyleResult.inspect().IsSet()) {
1180 pointToInsert = setStyleResult.unwrap();
1183 if (NS_WARN_IF(!pointToInsert.IsSetAndValid()) ||
1184 NS_WARN_IF(!pointToInsert.IsInContentNode())) {
1185 return Err(NS_ERROR_FAILURE);
1187 MOZ_ASSERT(pointToInsert.IsSetAndValid());
1189 // If the point is not in an element which can contain text nodes, climb up
1190 // the DOM tree.
1191 if (!pointToInsert.IsInTextNode()) {
1192 while (!HTMLEditUtils::CanNodeContain(*pointToInsert.GetContainer(),
1193 *nsGkAtoms::textTagName)) {
1194 if (NS_WARN_IF(pointToInsert.GetContainer() == editingHost) ||
1195 NS_WARN_IF(!pointToInsert.GetContainerParentAs<nsIContent>())) {
1196 NS_WARNING("Selection start point couldn't have text nodes");
1197 return Err(NS_ERROR_FAILURE);
1199 pointToInsert.Set(pointToInsert.ContainerAs<nsIContent>());
1203 if (aEditSubAction == EditSubAction::eInsertTextComingFromIME) {
1204 auto compositionStartPoint =
1205 GetFirstIMESelectionStartPoint<EditorDOMPoint>();
1206 if (!compositionStartPoint.IsSet()) {
1207 compositionStartPoint = pointToInsert;
1210 if (aInsertionString.IsEmpty()) {
1211 // Right now the WhiteSpaceVisibilityKeeper code bails on empty strings,
1212 // but IME needs the InsertTextWithTransaction() call to still happen
1213 // since empty strings are meaningful there.
1214 Result<InsertTextResult, nsresult> insertTextResult =
1215 InsertTextWithTransaction(*document, aInsertionString,
1216 compositionStartPoint,
1217 InsertTextTo::ExistingTextNodeIfAvailable);
1218 if (MOZ_UNLIKELY(insertTextResult.isErr())) {
1219 NS_WARNING("HTMLEditor::InsertTextWithTransaction() failed");
1220 return insertTextResult.propagateErr();
1222 nsresult rv = insertTextResult.unwrap().SuggestCaretPointTo(
1223 *this, {SuggestCaret::OnlyIfHasSuggestion,
1224 SuggestCaret::OnlyIfTransactionsAllowedToDoIt,
1225 SuggestCaret::AndIgnoreTrivialError});
1226 if (NS_FAILED(rv)) {
1227 NS_WARNING("CaretPoint::SuggestCaretPointTo() failed");
1228 return Err(rv);
1230 NS_WARNING_ASSERTION(
1231 rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
1232 "CaretPoint::SuggestCaretPointTo() failed, but ignored");
1233 return EditActionResult::HandledResult();
1236 auto compositionEndPoint = GetLastIMESelectionEndPoint<EditorDOMPoint>();
1237 if (!compositionEndPoint.IsSet()) {
1238 compositionEndPoint = compositionStartPoint;
1240 Result<InsertTextResult, nsresult> replaceTextResult =
1241 WhiteSpaceVisibilityKeeper::ReplaceText(
1242 *this, aInsertionString,
1243 EditorDOMRange(compositionStartPoint, compositionEndPoint),
1244 InsertTextTo::ExistingTextNodeIfAvailable, *editingHost);
1245 if (MOZ_UNLIKELY(replaceTextResult.isErr())) {
1246 NS_WARNING("WhiteSpaceVisibilityKeeper::ReplaceText() failed");
1247 return replaceTextResult.propagateErr();
1249 // CompositionTransaction should've set selection so that we should ignore
1250 // caret suggestion.
1251 replaceTextResult.unwrap().IgnoreCaretPointSuggestion();
1253 compositionStartPoint = GetFirstIMESelectionStartPoint<EditorDOMPoint>();
1254 compositionEndPoint = GetLastIMESelectionEndPoint<EditorDOMPoint>();
1255 if (NS_WARN_IF(!compositionStartPoint.IsSet()) ||
1256 NS_WARN_IF(!compositionEndPoint.IsSet())) {
1257 // Mutation event listener has changed the DOM tree...
1258 return EditActionResult::HandledResult();
1260 nsresult rv = TopLevelEditSubActionDataRef().mChangedRange->SetStartAndEnd(
1261 compositionStartPoint.ToRawRangeBoundary(),
1262 compositionEndPoint.ToRawRangeBoundary());
1263 if (NS_FAILED(rv)) {
1264 NS_WARNING("nsRange::SetStartAndEnd() failed");
1265 return Err(rv);
1267 return EditActionResult::HandledResult();
1270 MOZ_ASSERT(aEditSubAction == EditSubAction::eInsertText);
1272 // find where we are
1273 EditorDOMPoint currentPoint(pointToInsert);
1275 // is our text going to be PREformatted?
1276 // We remember this so that we know how to handle tabs.
1277 const bool isWhiteSpaceCollapsible = !EditorUtils::IsWhiteSpacePreformatted(
1278 *pointToInsert.ContainerAs<nsIContent>());
1280 // turn off the edit listener: we know how to
1281 // build the "doc changed range" ourselves, and it's
1282 // must faster to do it once here than to track all
1283 // the changes one at a time.
1284 AutoRestore<bool> disableListener(
1285 EditSubActionDataRef().mAdjustChangedRangeFromListener);
1286 EditSubActionDataRef().mAdjustChangedRangeFromListener = false;
1288 // don't change my selection in subtransactions
1289 AutoTransactionsConserveSelection dontChangeMySelection(*this);
1291 AutoTrackDOMPoint tracker(RangeUpdaterRef(), &pointToInsert);
1293 const auto GetInsertTextTo = [](int32_t aInclusiveNextLinefeedOffset,
1294 uint32_t aLineStartOffset) {
1295 if (aInclusiveNextLinefeedOffset > 0) {
1296 return aLineStartOffset > 0
1297 // If we'll insert a <br> and we're inserting 2nd or later
1298 // line, we should always create new `Text` since it'll be
1299 // between 2 <br> elements.
1300 ? InsertTextTo::AlwaysCreateNewTextNode
1301 // If we'll insert a <br> and we're inserting first line,
1302 // we should append text to preceding text node, but
1303 // we don't want to insert it to a a following text node
1304 // because of avoiding to split the `Text`.
1305 : InsertTextTo::ExistingTextNodeIfAvailableAndNotStart;
1307 // If we're inserting the last line, the text should be inserted to
1308 // start of the following `Text` if there is or middle of the `Text`
1309 // at insertion position if we're inserting only the line.
1310 return InsertTextTo::ExistingTextNodeIfAvailable;
1313 // for efficiency, break out the pre case separately. This is because
1314 // its a lot cheaper to search the input string for only newlines than
1315 // it is to search for both tabs and newlines.
1316 if (!isWhiteSpaceCollapsible || IsPlaintextMailComposer()) {
1317 uint32_t nextOffset = 0;
1318 while (nextOffset < aInsertionString.Length()) {
1319 const uint32_t lineStartOffset = nextOffset;
1320 const int32_t inclusiveNextLinefeedOffset =
1321 aInsertionString.FindChar(nsCRT::LF, lineStartOffset);
1322 const uint32_t lineLength =
1323 inclusiveNextLinefeedOffset != -1
1324 ? static_cast<uint32_t>(inclusiveNextLinefeedOffset) -
1325 lineStartOffset
1326 : aInsertionString.Length() - lineStartOffset;
1327 if (lineLength) {
1328 // lineText does not include the preformatted line break.
1329 const nsDependentSubstring lineText(aInsertionString, lineStartOffset,
1330 lineLength);
1331 Result<InsertTextResult, nsresult> insertTextResult =
1332 InsertTextWithTransaction(
1333 *document, lineText, currentPoint,
1334 GetInsertTextTo(inclusiveNextLinefeedOffset,
1335 lineStartOffset));
1336 if (MOZ_UNLIKELY(insertTextResult.isErr())) {
1337 NS_WARNING("HTMLEditor::InsertTextWithTransaction() failed");
1338 return insertTextResult.propagateErr();
1340 // Ignore the caret suggestion because of `dontChangeMySelection`
1341 // above.
1342 insertTextResult.inspect().IgnoreCaretPointSuggestion();
1343 if (insertTextResult.inspect().Handled()) {
1344 pointToInsert = currentPoint = insertTextResult.unwrap()
1345 .EndOfInsertedTextRef()
1346 .To<EditorDOMPoint>();
1347 } else {
1348 pointToInsert = currentPoint;
1350 if (inclusiveNextLinefeedOffset < 0) {
1351 break; // We reached the last line
1354 MOZ_ASSERT(inclusiveNextLinefeedOffset >= 0);
1355 Result<CreateElementResult, nsresult> insertBRElementResult =
1356 InsertBRElement(WithTransaction::Yes, currentPoint);
1357 if (MOZ_UNLIKELY(insertBRElementResult.isErr())) {
1358 NS_WARNING(
1359 "HTMLEditor::InsertBRElement(WithTransaction::Yes) failed");
1360 return insertBRElementResult.propagateErr();
1362 CreateElementResult unwrappedInsertBRElementResult =
1363 insertBRElementResult.unwrap();
1364 // We don't want to update selection here because we've blocked
1365 // InsertNodeTransaction updating selection with
1366 // dontChangeMySelection.
1367 unwrappedInsertBRElementResult.IgnoreCaretPointSuggestion();
1368 MOZ_ASSERT(!AllowsTransactionsToChangeSelection());
1370 nextOffset = inclusiveNextLinefeedOffset + 1;
1371 RefPtr<Element> brElement =
1372 unwrappedInsertBRElementResult.UnwrapNewNode();
1373 if (brElement->GetNextSibling()) {
1374 pointToInsert.Set(brElement->GetNextSibling());
1375 } else {
1376 pointToInsert.SetToEndOf(currentPoint.GetContainer());
1378 // XXX In most cases, pointToInsert and currentPoint are same here.
1379 // But if the <br> element has been moved to different point by
1380 // mutation observer, those points become different.
1381 currentPoint.SetAfter(brElement);
1382 NS_WARNING_ASSERTION(currentPoint.IsSet(),
1383 "Failed to set after the <br> element");
1384 NS_WARNING_ASSERTION(currentPoint == pointToInsert,
1385 "Perhaps, <br> element position has been moved to "
1386 "different point by mutation observer");
1388 } else {
1389 uint32_t nextOffset = 0;
1390 while (nextOffset < aInsertionString.Length()) {
1391 const uint32_t lineStartOffset = nextOffset;
1392 const int32_t inclusiveNextLinefeedOffset =
1393 aInsertionString.FindChar(nsCRT::LF, lineStartOffset);
1394 const uint32_t lineLength =
1395 inclusiveNextLinefeedOffset != -1
1396 ? static_cast<uint32_t>(inclusiveNextLinefeedOffset) -
1397 lineStartOffset
1398 : aInsertionString.Length() - lineStartOffset;
1400 if (lineLength) {
1401 auto insertTextResult =
1402 [&]() MOZ_CAN_RUN_SCRIPT -> Result<InsertTextResult, nsresult> {
1403 // lineText does not include the preformatted line break.
1404 const nsDependentSubstring lineText(aInsertionString,
1405 lineStartOffset, lineLength);
1406 if (!lineText.Contains(u'\t')) {
1407 return WhiteSpaceVisibilityKeeper::InsertText(
1408 *this, lineText, currentPoint,
1409 GetInsertTextTo(inclusiveNextLinefeedOffset, lineStartOffset),
1410 *editingHost);
1412 nsAutoString formattedLineText(lineText);
1413 formattedLineText.ReplaceSubstring(u"\t"_ns, u" "_ns);
1414 return WhiteSpaceVisibilityKeeper::InsertText(
1415 *this, formattedLineText, currentPoint,
1416 GetInsertTextTo(inclusiveNextLinefeedOffset, lineStartOffset),
1417 *editingHost);
1418 }();
1419 if (MOZ_UNLIKELY(insertTextResult.isErr())) {
1420 NS_WARNING("WhiteSpaceVisibilityKeeper::InsertText() failed");
1421 return insertTextResult.propagateErr();
1423 // Ignore the caret suggestion because of `dontChangeMySelection`
1424 // above.
1425 insertTextResult.inspect().IgnoreCaretPointSuggestion();
1426 if (insertTextResult.inspect().Handled()) {
1427 pointToInsert = currentPoint = insertTextResult.unwrap()
1428 .EndOfInsertedTextRef()
1429 .To<EditorDOMPoint>();
1430 } else {
1431 pointToInsert = currentPoint;
1433 if (inclusiveNextLinefeedOffset < 0) {
1434 break; // We reached the last line
1438 Result<CreateElementResult, nsresult> insertBRElementResult =
1439 WhiteSpaceVisibilityKeeper::InsertBRElement(*this, currentPoint,
1440 *editingHost);
1441 if (MOZ_UNLIKELY(insertBRElementResult.isErr())) {
1442 NS_WARNING("WhiteSpaceVisibilityKeeper::InsertBRElement() failed");
1443 return insertBRElementResult.propagateErr();
1445 CreateElementResult unwrappedInsertBRElementResult =
1446 insertBRElementResult.unwrap();
1447 // TODO: Some methods called for handling non-preformatted text use
1448 // ComputeEditingHost(). Therefore, they depend on the latest
1449 // selection. So we cannot skip updating selection here.
1450 nsresult rv = unwrappedInsertBRElementResult.SuggestCaretPointTo(
1451 *this, {SuggestCaret::OnlyIfHasSuggestion,
1452 SuggestCaret::OnlyIfTransactionsAllowedToDoIt,
1453 SuggestCaret::AndIgnoreTrivialError});
1454 if (NS_FAILED(rv)) {
1455 NS_WARNING("CreateElementResult::SuggestCaretPointTo() failed");
1456 return Err(rv);
1458 NS_WARNING_ASSERTION(
1459 rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
1460 "CreateElementResult::SuggestCaretPointTo() failed, but ignored");
1461 nextOffset = inclusiveNextLinefeedOffset + 1;
1462 RefPtr<Element> newBRElement =
1463 unwrappedInsertBRElementResult.UnwrapNewNode();
1464 MOZ_DIAGNOSTIC_ASSERT(newBRElement);
1465 if (newBRElement->GetNextSibling()) {
1466 pointToInsert.Set(newBRElement->GetNextSibling());
1467 } else {
1468 pointToInsert.SetToEndOf(currentPoint.GetContainer());
1470 currentPoint.SetAfter(newBRElement);
1471 NS_WARNING_ASSERTION(currentPoint.IsSet(),
1472 "Failed to set after the new <br> element");
1473 // XXX If the newBRElement has been moved or removed by mutation
1474 // observer, we hit this assert. We need to check if
1475 // newBRElement is in expected point, though, we must have
1476 // a lot of same bugs...
1477 NS_WARNING_ASSERTION(
1478 currentPoint == pointToInsert,
1479 "Perhaps, newBRElement has been moved or removed unexpectedly");
1483 // After this block, pointToInsert is updated by AutoTrackDOMPoint.
1486 if (currentPoint.IsSet()) {
1487 currentPoint.SetInterlinePosition(InterlinePosition::EndOfLine);
1488 nsresult rv = CollapseSelectionTo(currentPoint);
1489 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
1490 return Err(NS_ERROR_EDITOR_DESTROYED);
1492 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
1493 "Selection::Collapse() failed, but ignored");
1495 // manually update the doc changed range so that AfterEdit will clean up
1496 // the correct portion of the document.
1497 rv = TopLevelEditSubActionDataRef().mChangedRange->SetStartAndEnd(
1498 pointToInsert.ToRawRangeBoundary(), currentPoint.ToRawRangeBoundary());
1499 if (NS_FAILED(rv)) {
1500 NS_WARNING("nsRange::SetStartAndEnd() failed");
1501 return Err(rv);
1503 return EditActionResult::HandledResult();
1506 DebugOnly<nsresult> rvIgnored =
1507 SelectionRef().SetInterlinePosition(InterlinePosition::EndOfLine);
1508 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
1509 "Selection::SetInterlinePosition(InterlinePosition::"
1510 "EndOfLine) failed, but ignored");
1511 rv = TopLevelEditSubActionDataRef().mChangedRange->CollapseTo(pointToInsert);
1512 if (NS_FAILED(rv)) {
1513 NS_WARNING("nsRange::CollapseTo() failed");
1514 return Err(rv);
1516 return EditActionResult::HandledResult();
1519 nsresult HTMLEditor::InsertLineBreakAsSubAction() {
1520 MOZ_ASSERT(IsEditActionDataAvailable());
1521 MOZ_ASSERT(!IsSelectionRangeContainerNotContent());
1523 if (NS_WARN_IF(!mInitSucceeded)) {
1524 return NS_ERROR_NOT_INITIALIZED;
1528 Result<EditActionResult, nsresult> result = CanHandleHTMLEditSubAction();
1529 if (MOZ_UNLIKELY(result.isErr())) {
1530 NS_WARNING("HTMLEditor::CanHandleHTMLEditSubAction() failed");
1531 return result.unwrapErr();
1533 if (result.inspect().Canceled()) {
1534 return NS_OK;
1538 // XXX This may be called by execCommand() with "insertLineBreak".
1539 // In such case, naming the transaction "TypingTxnName" is odd.
1540 AutoPlaceholderBatch treatAsOneTransaction(*this, *nsGkAtoms::TypingTxnName,
1541 ScrollSelectionIntoView::Yes,
1542 __FUNCTION__);
1544 // calling it text insertion to trigger moz br treatment by rules
1545 // XXX Why do we use EditSubAction::eInsertText here? Looks like
1546 // EditSubAction::eInsertLineBreak or EditSubAction::eInsertNode
1547 // is better.
1548 IgnoredErrorResult ignoredError;
1549 AutoEditSubActionNotifier startToHandleEditSubAction(
1550 *this, EditSubAction::eInsertText, nsIEditor::eNext, ignoredError);
1551 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
1552 return ignoredError.StealNSResult();
1554 NS_WARNING_ASSERTION(
1555 !ignoredError.Failed(),
1556 "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
1558 UndefineCaretBidiLevel();
1560 // If the selection isn't collapsed, delete it.
1561 if (!SelectionRef().IsCollapsed()) {
1562 nsresult rv =
1563 DeleteSelectionAsSubAction(nsIEditor::eNone, nsIEditor::eStrip);
1564 if (NS_FAILED(rv)) {
1565 NS_WARNING(
1566 "EditorBase::DeleteSelectionAsSubAction(eNone, eStrip) failed");
1567 return rv;
1571 const nsRange* firstRange = SelectionRef().GetRangeAt(0);
1572 if (NS_WARN_IF(!firstRange)) {
1573 return NS_ERROR_FAILURE;
1576 EditorDOMPoint atStartOfSelection(firstRange->StartRef());
1577 if (NS_WARN_IF(!atStartOfSelection.IsSet())) {
1578 return NS_ERROR_FAILURE;
1580 MOZ_ASSERT(atStartOfSelection.IsSetAndValid());
1582 RefPtr<Element> editingHost = ComputeEditingHost();
1583 if (NS_WARN_IF(!editingHost)) {
1584 return NS_ERROR_FAILURE;
1587 // For backward compatibility, we should not insert a linefeed if
1588 // paragraph separator is set to "br" which is Gecko-specific mode.
1589 if (GetDefaultParagraphSeparator() == ParagraphSeparator::br ||
1590 !HTMLEditUtils::ShouldInsertLinefeedCharacter(atStartOfSelection,
1591 *editingHost)) {
1592 Result<CreateElementResult, nsresult> insertBRElementResult =
1593 InsertBRElement(WithTransaction::Yes, atStartOfSelection,
1594 nsIEditor::eNext);
1595 if (MOZ_UNLIKELY(insertBRElementResult.isErr())) {
1596 NS_WARNING("HTMLEditor::InsertBRElement(WithTransaction::Yes) failed");
1597 return insertBRElementResult.unwrapErr();
1599 CreateElementResult unwrappedInsertBRElementResult =
1600 insertBRElementResult.unwrap();
1601 MOZ_ASSERT(unwrappedInsertBRElementResult.GetNewNode());
1602 unwrappedInsertBRElementResult.IgnoreCaretPointSuggestion();
1604 auto pointToPutCaret =
1605 EditorDOMPoint::After(*unwrappedInsertBRElementResult.GetNewNode());
1606 if (MOZ_UNLIKELY(!pointToPutCaret.IsSet())) {
1607 NS_WARNING("Inserted <br> was unexpectedly removed");
1608 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
1610 const WSScanResult backwardScanFromBeforeBRElementResult =
1611 WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary(
1612 editingHost,
1613 EditorDOMPoint(unwrappedInsertBRElementResult.GetNewNode()),
1614 BlockInlineCheck::UseComputedDisplayStyle);
1615 if (MOZ_UNLIKELY(backwardScanFromBeforeBRElementResult.Failed())) {
1616 NS_WARNING(
1617 "WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary() failed");
1618 return Err(NS_ERROR_FAILURE);
1621 const WSScanResult forwardScanFromAfterBRElementResult =
1622 WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary(
1623 editingHost, pointToPutCaret,
1624 BlockInlineCheck::UseComputedDisplayStyle);
1625 if (MOZ_UNLIKELY(forwardScanFromAfterBRElementResult.Failed())) {
1626 NS_WARNING("WSRunScanner::ScanNextVisibleNodeOrBlockBoundary() failed");
1627 return Err(NS_ERROR_FAILURE);
1629 const bool brElementIsAfterBlock =
1630 backwardScanFromBeforeBRElementResult.ReachedBlockBoundary() ||
1631 // FIXME: This is wrong considering because the inline editing host may
1632 // be surrounded by visible inline content. However, WSRunScanner is
1633 // not aware of block boundary around it and stopping this change causes
1634 // starting to fail some WPT. Therefore, we need to keep doing this for
1635 // now.
1636 backwardScanFromBeforeBRElementResult
1637 .ReachedInlineEditingHostBoundary();
1638 const bool brElementIsBeforeBlock =
1639 forwardScanFromAfterBRElementResult.ReachedBlockBoundary() ||
1640 // FIXME: See above comment.
1641 forwardScanFromAfterBRElementResult.ReachedInlineEditingHostBoundary();
1642 const bool isEmptyEditingHost = HTMLEditUtils::IsEmptyNode(
1643 *editingHost, {EmptyCheckOption::TreatNonEditableContentAsInvisible});
1644 if (brElementIsBeforeBlock &&
1645 (isEmptyEditingHost || !brElementIsAfterBlock)) {
1646 // Empty last line is invisible if it's immediately before either parent
1647 // or another block's boundary so that we need to put invisible <br>
1648 // element here for making it visible.
1649 Result<CreateElementResult, nsresult> invisibleAdditionalBRElementResult =
1650 WhiteSpaceVisibilityKeeper::InsertBRElement(*this, pointToPutCaret,
1651 *editingHost);
1652 if (MOZ_UNLIKELY(invisibleAdditionalBRElementResult.isErr())) {
1653 NS_WARNING("WhiteSpaceVisibilityKeeper::InsertBRElement() failed");
1654 return invisibleAdditionalBRElementResult.unwrapErr();
1656 CreateElementResult unwrappedInvisibleAdditionalBRElement =
1657 invisibleAdditionalBRElementResult.unwrap();
1658 pointToPutCaret.Set(unwrappedInvisibleAdditionalBRElement.GetNewNode());
1659 unwrappedInvisibleAdditionalBRElement.IgnoreCaretPointSuggestion();
1660 } else if (forwardScanFromAfterBRElementResult
1661 .InVisibleOrCollapsibleCharacters()) {
1662 pointToPutCaret = forwardScanFromAfterBRElementResult
1663 .PointAtReachedContent<EditorDOMPoint>();
1664 } else if (forwardScanFromAfterBRElementResult.ReachedSpecialContent()) {
1665 // Next inserting text should be inserted into styled inline elements if
1666 // they have first visible thing in the new line.
1667 pointToPutCaret = forwardScanFromAfterBRElementResult
1668 .PointAtReachedContent<EditorDOMPoint>();
1671 nsresult rv = CollapseSelectionTo(pointToPutCaret);
1672 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
1673 "CreateElementResult::SuggestCaretPointTo() failed");
1674 return rv;
1677 nsresult rv = EnsureNoPaddingBRElementForEmptyEditor();
1678 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
1679 return NS_ERROR_EDITOR_DESTROYED;
1681 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
1682 "EditorBase::EnsureNoPaddingBRElementForEmptyEditor() "
1683 "failed, but ignored");
1685 if (NS_SUCCEEDED(rv) && SelectionRef().IsCollapsed()) {
1686 nsresult rv = EnsureCaretNotAfterInvisibleBRElement();
1687 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
1688 return NS_ERROR_EDITOR_DESTROYED;
1690 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
1691 "HTMLEditor::EnsureCaretNotAfterInvisibleBRElement() "
1692 "failed, but ignored");
1693 if (NS_SUCCEEDED(rv)) {
1694 nsresult rv = PrepareInlineStylesForCaret();
1695 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
1696 return NS_ERROR_EDITOR_DESTROYED;
1698 NS_WARNING_ASSERTION(
1699 NS_SUCCEEDED(rv),
1700 "HTMLEditor::PrepareInlineStylesForCaret() failed, but ignored");
1704 firstRange = SelectionRef().GetRangeAt(0);
1705 if (NS_WARN_IF(!firstRange)) {
1706 return NS_ERROR_FAILURE;
1709 atStartOfSelection = EditorDOMPoint(firstRange->StartRef());
1710 if (NS_WARN_IF(!atStartOfSelection.IsSet())) {
1711 return NS_ERROR_FAILURE;
1713 MOZ_ASSERT(atStartOfSelection.IsSetAndValid());
1715 // Do nothing if the node is read-only
1716 if (!HTMLEditUtils::IsSimplyEditableNode(
1717 *atStartOfSelection.GetContainer())) {
1718 return NS_SUCCESS_DOM_NO_OPERATION;
1721 Result<EditorDOMPoint, nsresult> insertLineFeedResult =
1722 HandleInsertLinefeed(atStartOfSelection, *editingHost);
1723 if (MOZ_UNLIKELY(insertLineFeedResult.isErr())) {
1724 NS_WARNING("HTMLEditor::HandleInsertLinefeed() failed");
1725 return insertLineFeedResult.unwrapErr();
1727 rv = CollapseSelectionTo(insertLineFeedResult.inspect());
1728 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
1729 "EditorBase::CollapseSelectionTo() failed");
1730 return rv;
1733 Result<EditActionResult, nsresult>
1734 HTMLEditor::InsertParagraphSeparatorAsSubAction(const Element& aEditingHost) {
1735 if (NS_WARN_IF(!mInitSucceeded)) {
1736 return Err(NS_ERROR_NOT_INITIALIZED);
1740 Result<EditActionResult, nsresult> result = CanHandleHTMLEditSubAction(
1741 CheckSelectionInReplacedElement::OnlyWhenNotInSameNode);
1742 if (MOZ_UNLIKELY(result.isErr())) {
1743 NS_WARNING("HTMLEditor::CanHandleHTMLEditSubAction() failed");
1744 return result;
1746 if (result.inspect().Canceled()) {
1747 return result;
1751 // XXX This may be called by execCommand() with "insertParagraph".
1752 // In such case, naming the transaction "TypingTxnName" is odd.
1753 AutoPlaceholderBatch treatAsOneTransaction(*this, *nsGkAtoms::TypingTxnName,
1754 ScrollSelectionIntoView::Yes,
1755 __FUNCTION__);
1757 IgnoredErrorResult ignoredError;
1758 AutoEditSubActionNotifier startToHandleEditSubAction(
1759 *this, EditSubAction::eInsertParagraphSeparator, nsIEditor::eNext,
1760 ignoredError);
1761 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
1762 return Err(ignoredError.StealNSResult());
1764 NS_WARNING_ASSERTION(
1765 !ignoredError.Failed(),
1766 "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
1768 UndefineCaretBidiLevel();
1770 // If the selection isn't collapsed, delete it.
1771 if (!SelectionRef().IsCollapsed()) {
1772 nsresult rv =
1773 DeleteSelectionAsSubAction(nsIEditor::eNone, nsIEditor::eStrip);
1774 if (NS_FAILED(rv)) {
1775 NS_WARNING(
1776 "EditorBase::DeleteSelectionAsSubAction(eNone, eStrip) failed");
1777 return Err(rv);
1781 nsresult rv = EnsureNoPaddingBRElementForEmptyEditor();
1782 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
1783 return Err(NS_ERROR_EDITOR_DESTROYED);
1785 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
1786 "EditorBase::EnsureNoPaddingBRElementForEmptyEditor() "
1787 "failed, but ignored");
1789 if (NS_SUCCEEDED(rv) && SelectionRef().IsCollapsed()) {
1790 nsresult rv = EnsureCaretNotAfterInvisibleBRElement();
1791 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
1792 return Err(NS_ERROR_EDITOR_DESTROYED);
1794 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
1795 "HTMLEditor::EnsureCaretNotAfterInvisibleBRElement() "
1796 "failed, but ignored");
1797 if (NS_SUCCEEDED(rv)) {
1798 nsresult rv = PrepareInlineStylesForCaret();
1799 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
1800 return Err(NS_ERROR_EDITOR_DESTROYED);
1802 NS_WARNING_ASSERTION(
1803 NS_SUCCEEDED(rv),
1804 "HTMLEditor::PrepareInlineStylesForCaret() failed, but ignored");
1808 AutoRangeArray selectionRanges(SelectionRef());
1810 // If the editing host is the body element, the selection may be outside
1811 // aEditingHost. In the case, we should use the editing host outside the
1812 // <body> only here for keeping our traditional behavior for now.
1813 // This should be fixed in bug 1634351.
1814 const Element* editingHostMaybeOutsideBody = &aEditingHost;
1815 if (aEditingHost.IsHTMLElement(nsGkAtoms::body)) {
1816 editingHostMaybeOutsideBody = ComputeEditingHost(LimitInBodyElement::No);
1817 if (NS_WARN_IF(!editingHostMaybeOutsideBody)) {
1818 return Err(NS_ERROR_FAILURE);
1821 selectionRanges.EnsureOnlyEditableRanges(*editingHostMaybeOutsideBody);
1822 if (NS_WARN_IF(selectionRanges.Ranges().IsEmpty())) {
1823 return Err(NS_ERROR_FAILURE);
1827 auto pointToInsert =
1828 selectionRanges.GetFirstRangeStartPoint<EditorDOMPoint>();
1829 if (NS_WARN_IF(!pointToInsert.IsInContentNode())) {
1830 return Err(NS_ERROR_FAILURE);
1832 while (true) {
1833 Element* element = pointToInsert.GetContainerOrContainerParentElement();
1834 if (MOZ_UNLIKELY(!element)) {
1835 return Err(NS_ERROR_FAILURE);
1837 // If the element can have a <br> element (it means that the element or its
1838 // container must be able to have <div> or <p> too), we can handle
1839 // insertParagraph at the point.
1840 if (HTMLEditUtils::CanNodeContain(*element, *nsGkAtoms::br)) {
1841 break;
1843 // Otherwise, try to insert paragraph at the parent.
1844 pointToInsert = pointToInsert.ParentPoint();
1847 if (IsMailEditor()) {
1848 if (RefPtr<Element> mailCiteElement = GetMostDistantAncestorMailCiteElement(
1849 *pointToInsert.ContainerAs<nsIContent>())) {
1850 // Split any mailcites in the way. Should we abort this if we encounter
1851 // table cell boundaries?
1852 Result<EditorDOMPoint, nsresult> atNewBRElementOrError =
1853 HandleInsertParagraphInMailCiteElement(*mailCiteElement,
1854 pointToInsert, aEditingHost);
1855 if (MOZ_UNLIKELY(atNewBRElementOrError.isErr())) {
1856 NS_WARNING(
1857 "HTMLEditor::HandleInsertParagraphInMailCiteElement() failed");
1858 return atNewBRElementOrError.propagateErr();
1860 EditorDOMPoint pointToPutCaret = atNewBRElementOrError.unwrap();
1861 MOZ_ASSERT(pointToPutCaret.IsSet());
1862 pointToPutCaret.SetInterlinePosition(InterlinePosition::StartOfNextLine);
1863 MOZ_ASSERT(pointToPutCaret.GetChild());
1864 MOZ_ASSERT(pointToPutCaret.GetChild()->IsHTMLElement(nsGkAtoms::br));
1865 nsresult rv = CollapseSelectionTo(pointToPutCaret);
1866 if (NS_FAILED(rv)) {
1867 NS_WARNING("EditorBase::CollapseSelectionTo() failed");
1868 return Err(rv);
1870 return EditActionResult::HandledResult();
1874 // If the active editing host is an inline element, or if the active editing
1875 // host is the block parent itself and we're configured to use <br> as a
1876 // paragraph separator, just append a <br>.
1877 // If the editing host parent element is editable, it means that the editing
1878 // host must be a <body> element and the selection may be outside the body
1879 // element. If the selection is outside the editing host, we should not
1880 // insert new paragraph nor <br> element.
1881 // XXX Currently, we don't support editing outside <body> element, but Blink
1882 // does it.
1883 if (aEditingHost.GetParentElement() &&
1884 HTMLEditUtils::IsSimplyEditableNode(*aEditingHost.GetParentElement()) &&
1885 !nsContentUtils::ContentIsFlattenedTreeDescendantOf(
1886 pointToInsert.ContainerAs<nsIContent>(), &aEditingHost)) {
1887 return Err(NS_ERROR_EDITOR_NO_EDITABLE_RANGE);
1890 auto InsertLineBreakInstead =
1891 [](const Element* aEditableBlockElement,
1892 const EditorDOMPoint& aCandidatePointToSplit,
1893 ParagraphSeparator aDefaultParagraphSeparator,
1894 const Element& aEditingHost) {
1895 // If there is no block parent in the editing host, i.e., the editing
1896 // host itself is also a non-block element, we should insert a line
1897 // break.
1898 if (!aEditableBlockElement) {
1899 // XXX Chromium checks if the CSS box of the editing host is a block.
1900 return true;
1903 // If the editable block element is not splittable, e.g., it's an
1904 // editing host, and the default paragraph separator is <br> or the
1905 // element cannot contain a <p> element, we should insert a <br>
1906 // element.
1907 if (!HTMLEditUtils::IsSplittableNode(*aEditableBlockElement)) {
1908 return aDefaultParagraphSeparator == ParagraphSeparator::br ||
1909 !HTMLEditUtils::CanElementContainParagraph(
1910 *aEditableBlockElement) ||
1911 (HTMLEditUtils::ShouldInsertLinefeedCharacter(
1912 aCandidatePointToSplit, aEditingHost) &&
1913 HTMLEditUtils::IsDisplayOutsideInline(aEditingHost));
1916 // If the nearest block parent is a single-line container declared in
1917 // the execCommand spec and not the editing host, we should separate the
1918 // block even if the default paragraph separator is <br> element.
1919 if (HTMLEditUtils::IsSingleLineContainer(*aEditableBlockElement)) {
1920 return false;
1923 // Otherwise, unless there is no block ancestor which can contain <p>
1924 // element, we shouldn't insert a line break here.
1925 for (const Element* editableBlockAncestor = aEditableBlockElement;
1926 editableBlockAncestor;
1927 editableBlockAncestor = HTMLEditUtils::GetAncestorElement(
1928 *editableBlockAncestor,
1929 HTMLEditUtils::ClosestEditableBlockElementOrButtonElement,
1930 BlockInlineCheck::UseComputedDisplayOutsideStyle)) {
1931 if (HTMLEditUtils::CanElementContainParagraph(
1932 *editableBlockAncestor)) {
1933 return false;
1936 return true;
1939 // Look for the nearest parent block. However, don't return error even if
1940 // there is no block parent here because in such case, i.e., editing host
1941 // is an inline element, we should insert <br> simply.
1942 RefPtr<Element> editableBlockElement =
1943 HTMLEditUtils::GetInclusiveAncestorElement(
1944 *pointToInsert.ContainerAs<nsIContent>(),
1945 HTMLEditUtils::ClosestEditableBlockElementOrButtonElement,
1946 BlockInlineCheck::UseComputedDisplayOutsideStyle);
1948 // If we cannot insert a <p>/<div> element at the selection, we should insert
1949 // a <br> element or a linefeed instead.
1950 const ParagraphSeparator separator = GetDefaultParagraphSeparator();
1951 if (InsertLineBreakInstead(editableBlockElement, pointToInsert, separator,
1952 aEditingHost)) {
1953 // For backward compatibility, we should not insert a linefeed if
1954 // paragraph separator is set to "br" which is Gecko-specific mode.
1955 if (separator != ParagraphSeparator::br &&
1956 HTMLEditUtils::ShouldInsertLinefeedCharacter(pointToInsert,
1957 aEditingHost)) {
1958 Result<EditorDOMPoint, nsresult> insertLineFeedResult =
1959 HandleInsertLinefeed(pointToInsert, aEditingHost);
1960 if (MOZ_UNLIKELY(insertLineFeedResult.isErr())) {
1961 NS_WARNING("HTMLEditor::HandleInsertLinefeed() failed");
1962 return insertLineFeedResult.propagateErr();
1964 nsresult rv = CollapseSelectionTo(insertLineFeedResult.inspect());
1965 if (NS_FAILED(rv)) {
1966 NS_WARNING("EditorBase::CollapseSelectionTo() failed");
1967 return Err(rv);
1969 return EditActionResult::HandledResult();
1972 Result<CreateElementResult, nsresult> insertBRElementResult =
1973 HandleInsertBRElement(pointToInsert, aEditingHost);
1974 if (MOZ_UNLIKELY(insertBRElementResult.isErr())) {
1975 NS_WARNING("HTMLEditor::HandleInsertBRElement() failed");
1976 return insertBRElementResult.propagateErr();
1978 nsresult rv =
1979 insertBRElementResult.inspect().SuggestCaretPointTo(*this, {});
1980 if (NS_FAILED(rv)) {
1981 NS_WARNING("CreateElementResult::SuggestCaretPointTo() failed");
1982 return Err(rv);
1984 return EditActionResult::HandledResult();
1987 // If somebody wants to restrict caret position in a block element below,
1988 // we should guarantee it. Otherwise, we can put caret to the candidate
1989 // point.
1990 auto CollapseSelection =
1991 [this](const EditorDOMPoint& aCandidatePointToPutCaret,
1992 const Element* aBlockElementShouldHaveCaret,
1993 const SuggestCaretOptions& aOptions)
1994 MOZ_CAN_RUN_SCRIPT -> nsresult {
1995 if (!aCandidatePointToPutCaret.IsSet()) {
1996 if (aOptions.contains(SuggestCaret::OnlyIfHasSuggestion)) {
1997 return NS_OK;
1999 return aOptions.contains(SuggestCaret::AndIgnoreTrivialError)
2000 ? NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR
2001 : NS_ERROR_FAILURE;
2003 EditorDOMPoint pointToPutCaret(aCandidatePointToPutCaret);
2004 if (aBlockElementShouldHaveCaret) {
2005 Result<EditorDOMPoint, nsresult> pointToPutCaretOrError =
2006 HTMLEditUtils::ComputePointToPutCaretInElementIfOutside<
2007 EditorDOMPoint>(*aBlockElementShouldHaveCaret,
2008 aCandidatePointToPutCaret);
2009 if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) {
2010 NS_WARNING(
2011 "HTMLEditUtils::ComputePointToPutCaretInElementIfOutside() "
2012 "failed, but ignored");
2013 } else if (pointToPutCaretOrError.inspect().IsSet()) {
2014 pointToPutCaret = pointToPutCaretOrError.unwrap();
2017 nsresult rv = CollapseSelectionTo(pointToPutCaret);
2018 if (NS_FAILED(rv) && MOZ_LIKELY(rv != NS_ERROR_EDITOR_DESTROYED) &&
2019 aOptions.contains(SuggestCaret::AndIgnoreTrivialError)) {
2020 rv = NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR;
2022 return rv;
2025 RefPtr<Element> blockElementToPutCaret;
2026 // If the default paragraph separator is not <br> and selection is not in
2027 // a splittable block element, we should wrap selected contents in a new
2028 // paragraph, then, split it.
2029 if (!HTMLEditUtils::IsSplittableNode(*editableBlockElement) &&
2030 separator != ParagraphSeparator::br) {
2031 MOZ_ASSERT(separator == ParagraphSeparator::div ||
2032 separator == ParagraphSeparator::p);
2033 // FIXME: If there is no splittable block element, the other browsers wrap
2034 // the right nodes into new paragraph, but keep the left node as-is.
2035 // We should follow them to make here simpler and better compatibility.
2036 Result<RefPtr<Element>, nsresult> suggestBlockElementToPutCaretOrError =
2037 FormatBlockContainerWithTransaction(
2038 selectionRanges,
2039 MOZ_KnownLive(HTMLEditor::ToParagraphSeparatorTagName(separator)),
2040 // For keeping the traditional behavior at insertParagraph command,
2041 // let's use the XUL paragraph state command targets even if we're
2042 // handling HTML insertParagraph command.
2043 FormatBlockMode::XULParagraphStateCommand, aEditingHost);
2044 if (MOZ_UNLIKELY(suggestBlockElementToPutCaretOrError.isErr())) {
2045 NS_WARNING("HTMLEditor::FormatBlockContainerWithTransaction() failed");
2046 return suggestBlockElementToPutCaretOrError.propagateErr();
2048 if (selectionRanges.HasSavedRanges()) {
2049 selectionRanges.RestoreFromSavedRanges();
2051 pointToInsert = selectionRanges.GetFirstRangeStartPoint<EditorDOMPoint>();
2052 if (NS_WARN_IF(!pointToInsert.IsInContentNode())) {
2053 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
2055 MOZ_ASSERT(pointToInsert.IsSetAndValid());
2056 blockElementToPutCaret = suggestBlockElementToPutCaretOrError.unwrap();
2058 editableBlockElement = HTMLEditUtils::GetInclusiveAncestorElement(
2059 *pointToInsert.ContainerAs<nsIContent>(),
2060 HTMLEditUtils::ClosestEditableBlockElementOrButtonElement,
2061 BlockInlineCheck::UseComputedDisplayOutsideStyle);
2062 if (NS_WARN_IF(!editableBlockElement)) {
2063 return Err(NS_ERROR_UNEXPECTED);
2065 if (NS_WARN_IF(!HTMLEditUtils::IsSplittableNode(*editableBlockElement))) {
2066 // Didn't create a new block for some reason, fall back to <br>
2067 Result<CreateElementResult, nsresult> insertBRElementResult =
2068 HandleInsertBRElement(pointToInsert, aEditingHost);
2069 if (MOZ_UNLIKELY(insertBRElementResult.isErr())) {
2070 NS_WARNING("HTMLEditor::HandleInsertBRElement() failed");
2071 return insertBRElementResult.propagateErr();
2073 CreateElementResult unwrappedInsertBRElementResult =
2074 insertBRElementResult.unwrap();
2075 EditorDOMPoint pointToPutCaret =
2076 unwrappedInsertBRElementResult.UnwrapCaretPoint();
2077 if (MOZ_UNLIKELY(!pointToPutCaret.IsSet())) {
2078 NS_WARNING(
2079 "HTMLEditor::HandleInsertBRElement() didn't suggest a point to put "
2080 "caret");
2081 return Err(NS_ERROR_FAILURE);
2083 nsresult rv =
2084 CollapseSelection(pointToPutCaret, blockElementToPutCaret, {});
2085 if (NS_FAILED(rv)) {
2086 NS_WARNING("CollapseSelection() failed");
2087 return Err(rv);
2089 return EditActionResult::HandledResult();
2091 // We want to collapse selection in the editable block element.
2092 blockElementToPutCaret = editableBlockElement;
2095 // If block is empty, populate with br. (For example, imagine a div that
2096 // contains the word "text". The user selects "text" and types return.
2097 // "Text" is deleted leaving an empty block. We want to put in one br to
2098 // make block have a line. Then code further below will put in a second br.)
2099 RefPtr<Element> insertedPaddingBRElement;
2100 if (HTMLEditUtils::IsEmptyBlockElement(
2101 *editableBlockElement,
2102 {EmptyCheckOption::TreatSingleBRElementAsVisible},
2103 BlockInlineCheck::UseComputedDisplayOutsideStyle)) {
2104 Result<CreateElementResult, nsresult> insertBRElementResult =
2105 InsertBRElement(WithTransaction::Yes,
2106 EditorDOMPoint::AtEndOf(*editableBlockElement));
2107 if (MOZ_UNLIKELY(insertBRElementResult.isErr())) {
2108 NS_WARNING("HTMLEditor::InsertBRElement(WithTransaction::Yes) failed");
2109 return insertBRElementResult.propagateErr();
2111 CreateElementResult unwrappedInsertBRElementResult =
2112 insertBRElementResult.unwrap();
2113 unwrappedInsertBRElementResult.IgnoreCaretPointSuggestion();
2114 MOZ_ASSERT(unwrappedInsertBRElementResult.GetNewNode());
2115 insertedPaddingBRElement = unwrappedInsertBRElementResult.UnwrapNewNode();
2117 pointToInsert = selectionRanges.GetFirstRangeStartPoint<EditorDOMPoint>();
2118 if (NS_WARN_IF(!pointToInsert.IsInContentNode())) {
2119 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
2123 RefPtr<Element> maybeNonEditableListItem =
2124 HTMLEditUtils::GetClosestAncestorListItemElement(*editableBlockElement,
2125 &aEditingHost);
2126 if (maybeNonEditableListItem &&
2127 HTMLEditUtils::IsSplittableNode(*maybeNonEditableListItem)) {
2128 Result<InsertParagraphResult, nsresult> insertParagraphInListItemResult =
2129 HandleInsertParagraphInListItemElement(*maybeNonEditableListItem,
2130 pointToInsert, aEditingHost);
2131 if (MOZ_UNLIKELY(insertParagraphInListItemResult.isErr())) {
2132 if (NS_WARN_IF(insertParagraphInListItemResult.unwrapErr() ==
2133 NS_ERROR_EDITOR_DESTROYED)) {
2134 return Err(NS_ERROR_EDITOR_DESTROYED);
2136 NS_WARNING(
2137 "HTMLEditor::HandleInsertParagraphInListItemElement() failed, but "
2138 "ignored");
2139 return EditActionResult::HandledResult();
2141 InsertParagraphResult unwrappedInsertParagraphInListItemResult =
2142 insertParagraphInListItemResult.unwrap();
2143 MOZ_ASSERT(unwrappedInsertParagraphInListItemResult.Handled());
2144 MOZ_ASSERT(unwrappedInsertParagraphInListItemResult.GetNewNode());
2145 const RefPtr<Element> listItemOrParagraphElement =
2146 unwrappedInsertParagraphInListItemResult.UnwrapNewNode();
2147 const EditorDOMPoint pointToPutCaret =
2148 unwrappedInsertParagraphInListItemResult.UnwrapCaretPoint();
2149 nsresult rv = CollapseSelection(pointToPutCaret, listItemOrParagraphElement,
2150 {SuggestCaret::AndIgnoreTrivialError});
2151 if (NS_FAILED(rv)) {
2152 NS_WARNING("CollapseSelection() failed");
2153 return Err(rv);
2155 NS_WARNING_ASSERTION(rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
2156 "CollapseSelection() failed, but ignored");
2157 return EditActionResult::HandledResult();
2160 if (HTMLEditUtils::IsHeader(*editableBlockElement)) {
2161 Result<InsertParagraphResult, nsresult>
2162 insertParagraphInHeadingElementResult =
2163 HandleInsertParagraphInHeadingElement(*editableBlockElement,
2164 pointToInsert);
2165 if (MOZ_UNLIKELY(insertParagraphInHeadingElementResult.isErr())) {
2166 NS_WARNING(
2167 "HTMLEditor::HandleInsertParagraphInHeadingElement() failed, but "
2168 "ignored");
2169 return EditActionResult::HandledResult();
2171 InsertParagraphResult unwrappedInsertParagraphInHeadingElementResult =
2172 insertParagraphInHeadingElementResult.unwrap();
2173 if (unwrappedInsertParagraphInHeadingElementResult.Handled()) {
2174 MOZ_ASSERT(unwrappedInsertParagraphInHeadingElementResult.GetNewNode());
2175 blockElementToPutCaret =
2176 unwrappedInsertParagraphInHeadingElementResult.UnwrapNewNode();
2178 const EditorDOMPoint pointToPutCaret =
2179 unwrappedInsertParagraphInHeadingElementResult.UnwrapCaretPoint();
2180 nsresult rv = CollapseSelection(pointToPutCaret, blockElementToPutCaret,
2181 {SuggestCaret::OnlyIfHasSuggestion,
2182 SuggestCaret::AndIgnoreTrivialError});
2183 if (NS_FAILED(rv)) {
2184 NS_WARNING("CollapseSelection() failed");
2185 return Err(rv);
2187 NS_WARNING_ASSERTION(rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
2188 "CollapseSelection() failed, but ignored");
2189 return EditActionResult::HandledResult();
2192 // XXX Ideally, we should take same behavior with both <p> container and
2193 // <div> container. However, we are still using <br> as default
2194 // paragraph separator (non-standard) and we've split only <p> container
2195 // long time. Therefore, some web apps may depend on this behavior like
2196 // Gmail. So, let's use traditional odd behavior only when the default
2197 // paragraph separator is <br>. Otherwise, take consistent behavior
2198 // between <p> container and <div> container.
2199 if ((separator == ParagraphSeparator::br &&
2200 editableBlockElement->IsHTMLElement(nsGkAtoms::p)) ||
2201 (separator != ParagraphSeparator::br &&
2202 editableBlockElement->IsAnyOfHTMLElements(nsGkAtoms::p,
2203 nsGkAtoms::div))) {
2204 // Paragraphs: special rules to look for <br>s
2205 Result<SplitNodeResult, nsresult> splitNodeResult =
2206 HandleInsertParagraphInParagraph(
2207 *editableBlockElement,
2208 insertedPaddingBRElement ? EditorDOMPoint(insertedPaddingBRElement)
2209 : pointToInsert,
2210 aEditingHost);
2211 if (MOZ_UNLIKELY(splitNodeResult.isErr())) {
2212 NS_WARNING("HTMLEditor::HandleInsertParagraphInParagraph() failed");
2213 return splitNodeResult.propagateErr();
2215 if (splitNodeResult.inspect().Handled()) {
2216 SplitNodeResult unwrappedSplitNodeResult = splitNodeResult.unwrap();
2217 const RefPtr<Element> rightParagraphElement =
2218 unwrappedSplitNodeResult.DidSplit()
2219 ? unwrappedSplitNodeResult.GetNextContentAs<Element>()
2220 : blockElementToPutCaret.get();
2221 const EditorDOMPoint pointToPutCaret =
2222 unwrappedSplitNodeResult.UnwrapCaretPoint();
2223 nsresult rv = CollapseSelection(pointToPutCaret, rightParagraphElement,
2224 {SuggestCaret::AndIgnoreTrivialError});
2225 if (NS_FAILED(rv)) {
2226 NS_WARNING("CollapseSelection() failed");
2227 return Err(rv);
2229 NS_WARNING_ASSERTION(rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
2230 "CollapseSelection() failed, but ignored");
2231 return EditActionResult::HandledResult();
2233 MOZ_ASSERT(!splitNodeResult.inspect().HasCaretPointSuggestion());
2235 // Fall through, if HandleInsertParagraphInParagraph() didn't handle it.
2236 MOZ_ASSERT(pointToInsert.IsSetAndValid(),
2237 "HTMLEditor::HandleInsertParagraphInParagraph() shouldn't touch "
2238 "the DOM tree if it returns not-handled state");
2241 // If nobody handles this edit action, let's insert new <br> at the selection.
2242 Result<CreateElementResult, nsresult> insertBRElementResult =
2243 HandleInsertBRElement(pointToInsert, aEditingHost);
2244 if (MOZ_UNLIKELY(insertBRElementResult.isErr())) {
2245 NS_WARNING("HTMLEditor::HandleInsertBRElement() failed");
2246 return insertBRElementResult.propagateErr();
2248 CreateElementResult unwrappedInsertBRElementResult =
2249 insertBRElementResult.unwrap();
2250 EditorDOMPoint pointToPutCaret =
2251 unwrappedInsertBRElementResult.UnwrapCaretPoint();
2252 rv = CollapseSelection(pointToPutCaret, blockElementToPutCaret, {});
2253 if (NS_FAILED(rv)) {
2254 NS_WARNING("CreateElementResult::SuggestCaretPointTo() failed");
2255 return Err(rv);
2257 return EditActionResult::HandledResult();
2260 Result<CreateElementResult, nsresult> HTMLEditor::HandleInsertBRElement(
2261 const EditorDOMPoint& aPointToBreak, const Element& aEditingHost) {
2262 MOZ_ASSERT(aPointToBreak.IsSet());
2263 MOZ_ASSERT(IsEditActionDataAvailable());
2265 const bool editingHostIsEmpty = HTMLEditUtils::IsEmptyNode(
2266 aEditingHost, {EmptyCheckOption::TreatNonEditableContentAsInvisible});
2267 WSRunScanner wsRunScanner(&aEditingHost, aPointToBreak,
2268 BlockInlineCheck::UseComputedDisplayStyle);
2269 const WSScanResult backwardScanResult =
2270 wsRunScanner.ScanPreviousVisibleNodeOrBlockBoundaryFrom(aPointToBreak);
2271 if (MOZ_UNLIKELY(backwardScanResult.Failed())) {
2272 NS_WARNING(
2273 "WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundaryFrom() failed");
2274 return Err(NS_ERROR_FAILURE);
2276 const bool brElementIsAfterBlock =
2277 backwardScanResult.ReachedBlockBoundary() ||
2278 // FIXME: This is wrong considering because the inline editing host may
2279 // be surrounded by visible inline content. However, WSRunScanner is
2280 // not aware of block boundary around it and stopping this change causes
2281 // starting to fail some WPT. Therefore, we need to keep doing this for
2282 // now.
2283 backwardScanResult.ReachedInlineEditingHostBoundary();
2284 const WSScanResult forwardScanResult =
2285 wsRunScanner.ScanInclusiveNextVisibleNodeOrBlockBoundaryFrom(
2286 aPointToBreak);
2287 if (MOZ_UNLIKELY(forwardScanResult.Failed())) {
2288 NS_WARNING("WSRunScanner::ScanNextVisibleNodeOrBlockBoundaryFrom() failed");
2289 return Err(NS_ERROR_FAILURE);
2291 const bool brElementIsBeforeBlock =
2292 forwardScanResult.ReachedBlockBoundary() ||
2293 // FIXME: See above comment
2294 forwardScanResult.ReachedInlineEditingHostBoundary();
2296 // First, insert a <br> element.
2297 RefPtr<Element> brElement;
2298 if (IsPlaintextMailComposer()) {
2299 Result<CreateElementResult, nsresult> insertBRElementResult =
2300 InsertBRElement(WithTransaction::Yes, aPointToBreak);
2301 if (MOZ_UNLIKELY(insertBRElementResult.isErr())) {
2302 NS_WARNING("HTMLEditor::InsertBRElement(WithTransaction::Yes) failed");
2303 return insertBRElementResult;
2305 CreateElementResult unwrappedInsertBRElementResult =
2306 insertBRElementResult.unwrap();
2307 // We'll return with suggesting new caret position and nobody refers
2308 // selection after here. So we don't need to update selection here.
2309 unwrappedInsertBRElementResult.IgnoreCaretPointSuggestion();
2310 MOZ_ASSERT(unwrappedInsertBRElementResult.GetNewNode());
2311 brElement = unwrappedInsertBRElementResult.UnwrapNewNode();
2312 } else {
2313 EditorDOMPoint pointToBreak(aPointToBreak);
2314 // If the container of the break is a link, we need to split it and
2315 // insert new <br> between the split links.
2316 RefPtr<Element> linkNode =
2317 HTMLEditor::GetLinkElement(pointToBreak.GetContainer());
2318 if (linkNode) {
2319 Result<SplitNodeResult, nsresult> splitLinkNodeResult =
2320 SplitNodeDeepWithTransaction(
2321 *linkNode, pointToBreak,
2322 SplitAtEdges::eDoNotCreateEmptyContainer);
2323 if (MOZ_UNLIKELY(splitLinkNodeResult.isErr())) {
2324 NS_WARNING(
2325 "HTMLEditor::SplitNodeDeepWithTransaction(SplitAtEdges::"
2326 "eDoNotCreateEmptyContainer) failed");
2327 return splitLinkNodeResult.propagateErr();
2329 // TODO: Some methods called by
2330 // WhiteSpaceVisibilityKeeper::InsertBRElement() use
2331 // ComputeEditingHost() which depends on selection. Therefore,
2332 // we cannot skip updating selection here.
2333 nsresult rv = splitLinkNodeResult.inspect().SuggestCaretPointTo(
2334 *this, {SuggestCaret::OnlyIfHasSuggestion,
2335 SuggestCaret::OnlyIfTransactionsAllowedToDoIt});
2336 if (NS_FAILED(rv)) {
2337 NS_WARNING("SplitNodeResult::SuggestCaretPointTo() failed");
2338 return Err(rv);
2340 pointToBreak =
2341 splitLinkNodeResult.inspect().AtSplitPoint<EditorDOMPoint>();
2343 Result<CreateElementResult, nsresult> insertBRElementResult =
2344 WhiteSpaceVisibilityKeeper::InsertBRElement(*this, pointToBreak,
2345 aEditingHost);
2346 if (MOZ_UNLIKELY(insertBRElementResult.isErr())) {
2347 NS_WARNING("WhiteSpaceVisibilityKeeper::InsertBRElement() failed");
2348 return insertBRElementResult;
2350 CreateElementResult unwrappedInsertBRElementResult =
2351 insertBRElementResult.unwrap();
2352 // We'll return with suggesting new caret position and nobody refers
2353 // selection after here. So we don't need to update selection here.
2354 unwrappedInsertBRElementResult.IgnoreCaretPointSuggestion();
2355 brElement = unwrappedInsertBRElementResult.UnwrapNewNode();
2356 MOZ_ASSERT(brElement);
2359 if (MOZ_UNLIKELY(!brElement->GetParentNode())) {
2360 NS_WARNING("Inserted <br> element was removed by the web app");
2361 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
2363 auto afterBRElement = EditorDOMPoint::After(brElement);
2365 auto InsertAdditionalInvisibleLineBreak =
2366 [&]() MOZ_CAN_RUN_SCRIPT -> Result<CreateElementResult, nsresult> {
2367 // Empty last line is invisible if it's immediately before either parent or
2368 // another block's boundary so that we need to put invisible <br> element
2369 // here for making it visible.
2370 Result<CreateElementResult, nsresult> invisibleAdditionalBRElementResult =
2371 WhiteSpaceVisibilityKeeper::InsertBRElement(*this, afterBRElement,
2372 aEditingHost);
2373 if (MOZ_UNLIKELY(invisibleAdditionalBRElementResult.isErr())) {
2374 NS_WARNING("WhiteSpaceVisibilityKeeper::InsertBRElement() failed");
2375 return invisibleAdditionalBRElementResult;
2377 // afterBRElement points after the first <br> with referring an old child.
2378 // Therefore, we need to update it with new child which is the new invisible
2379 // <br>.
2380 afterBRElement.Set(
2381 invisibleAdditionalBRElementResult.inspect().GetNewNode());
2382 return invisibleAdditionalBRElementResult;
2385 if (brElementIsAfterBlock && brElementIsBeforeBlock) {
2386 // We just placed a <br> between block boundaries. This is the one case
2387 // where we want the selection to be before the br we just placed, as the
2388 // br will be on a new line, rather than at end of prior line.
2389 // XXX brElementIsAfterBlock and brElementIsBeforeBlock were set before
2390 // modifying the DOM tree. So, now, the <br> element may not be
2391 // between blocks.
2392 EditorDOMPoint pointToPutCaret;
2393 if (editingHostIsEmpty) {
2394 Result<CreateElementResult, nsresult> invisibleAdditionalBRElementResult =
2395 InsertAdditionalInvisibleLineBreak();
2396 if (invisibleAdditionalBRElementResult.isErr()) {
2397 return invisibleAdditionalBRElementResult;
2399 invisibleAdditionalBRElementResult.unwrap().IgnoreCaretPointSuggestion();
2400 pointToPutCaret = std::move(afterBRElement);
2401 } else {
2402 pointToPutCaret =
2403 EditorDOMPoint(brElement, InterlinePosition::StartOfNextLine);
2405 return CreateElementResult(std::move(brElement),
2406 std::move(pointToPutCaret));
2409 const WSScanResult forwardScanFromAfterBRElementResult =
2410 WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary(
2411 &aEditingHost, afterBRElement,
2412 BlockInlineCheck::UseComputedDisplayStyle);
2413 if (MOZ_UNLIKELY(forwardScanFromAfterBRElementResult.Failed())) {
2414 NS_WARNING("WSRunScanner::ScanNextVisibleNodeOrBlockBoundary() failed");
2415 return Err(NS_ERROR_FAILURE);
2417 if (forwardScanFromAfterBRElementResult.ReachedBRElement()) {
2418 // The next thing after the break we inserted is another break. Move the
2419 // second break to be the first break's sibling. This will prevent them
2420 // from being in different inline nodes, which would break
2421 // SetInterlinePosition(). It will also assure that if the user clicks
2422 // away and then clicks back on their new blank line, they will still get
2423 // the style from the line above.
2424 if (brElement->GetNextSibling() !=
2425 forwardScanFromAfterBRElementResult.BRElementPtr()) {
2426 MOZ_ASSERT(forwardScanFromAfterBRElementResult.BRElementPtr());
2427 Result<MoveNodeResult, nsresult> moveBRElementResult =
2428 MoveNodeWithTransaction(
2429 MOZ_KnownLive(
2430 *forwardScanFromAfterBRElementResult.BRElementPtr()),
2431 afterBRElement);
2432 if (MOZ_UNLIKELY(moveBRElementResult.isErr())) {
2433 NS_WARNING("HTMLEditor::MoveNodeWithTransaction() failed");
2434 return moveBRElementResult.propagateErr();
2436 nsresult rv = moveBRElementResult.inspect().SuggestCaretPointTo(
2437 *this, {SuggestCaret::OnlyIfHasSuggestion,
2438 SuggestCaret::OnlyIfTransactionsAllowedToDoIt,
2439 SuggestCaret::AndIgnoreTrivialError});
2440 if (NS_FAILED(rv)) {
2441 NS_WARNING("MoveNodeResult::SuggestCaretPointTo() failed");
2442 return Err(rv);
2444 NS_WARNING_ASSERTION(
2445 rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
2446 "MoveNodeResult::SuggestCaretPointTo() failed, but ignored");
2448 } else if ((forwardScanFromAfterBRElementResult.ReachedBlockBoundary() ||
2449 // FIXME: This is wrong considering because the inline editing
2450 // host may be surrounded by visible inline content. However,
2451 // WSRunScanner is not aware of block boundary around it and
2452 // stopping this change causes starting to fail some WPT.
2453 // Therefore, we need to keep doing this for now.
2454 forwardScanFromAfterBRElementResult
2455 .ReachedInlineEditingHostBoundary()) &&
2456 !brElementIsAfterBlock) {
2457 Result<CreateElementResult, nsresult> invisibleAdditionalBRElementResult =
2458 InsertAdditionalInvisibleLineBreak();
2459 if (invisibleAdditionalBRElementResult.isErr()) {
2460 return invisibleAdditionalBRElementResult;
2462 invisibleAdditionalBRElementResult.unwrap().IgnoreCaretPointSuggestion();
2465 // We want the caret to stick to whatever is past the break. This is because
2466 // the break is on the same line we were on, but the next content will be on
2467 // the following line.
2469 // An exception to this is if the break has a next sibling that is a block
2470 // node. Then we stick to the left to avoid an uber caret.
2471 nsIContent* nextSiblingOfBRElement = brElement->GetNextSibling();
2472 afterBRElement.SetInterlinePosition(
2473 nextSiblingOfBRElement && HTMLEditUtils::IsBlockElement(
2474 *nextSiblingOfBRElement,
2475 BlockInlineCheck::UseComputedDisplayStyle)
2476 ? InterlinePosition::EndOfLine
2477 : InterlinePosition::StartOfNextLine);
2478 return CreateElementResult(std::move(brElement), afterBRElement);
2481 Result<EditorDOMPoint, nsresult> HTMLEditor::HandleInsertLinefeed(
2482 const EditorDOMPoint& aPointToBreak, const Element& aEditingHost) {
2483 MOZ_ASSERT(IsEditActionDataAvailable());
2485 if (NS_WARN_IF(!aPointToBreak.IsSet())) {
2486 return Err(NS_ERROR_INVALID_ARG);
2489 const RefPtr<Document> document = GetDocument();
2490 MOZ_DIAGNOSTIC_ASSERT(document);
2491 if (NS_WARN_IF(!document)) {
2492 return Err(NS_ERROR_FAILURE);
2495 // TODO: The following code is duplicated from `HandleInsertText`. They
2496 // should be merged when we fix bug 92921.
2498 Result<EditorDOMPoint, nsresult> setStyleResult =
2499 CreateStyleForInsertText(aPointToBreak, aEditingHost);
2500 if (MOZ_UNLIKELY(setStyleResult.isErr())) {
2501 NS_WARNING("HTMLEditor::CreateStyleForInsertText() failed");
2502 return setStyleResult.propagateErr();
2505 EditorDOMPoint pointToInsert = setStyleResult.inspect().IsSet()
2506 ? setStyleResult.inspect()
2507 : aPointToBreak;
2508 if (NS_WARN_IF(!pointToInsert.IsSetAndValid()) ||
2509 NS_WARN_IF(!pointToInsert.IsInContentNode())) {
2510 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
2512 MOZ_ASSERT(pointToInsert.IsSetAndValid());
2514 // The node may not be able to have a text node so that we need to check it
2515 // here.
2516 if (!pointToInsert.IsInTextNode() &&
2517 !HTMLEditUtils::CanNodeContain(*pointToInsert.ContainerAs<nsIContent>(),
2518 *nsGkAtoms::textTagName)) {
2519 NS_WARNING(
2520 "HTMLEditor::HandleInsertLinefeed() couldn't insert a linefeed because "
2521 "the insertion position couldn't have text nodes");
2522 return Err(NS_ERROR_EDITOR_NO_EDITABLE_RANGE);
2525 AutoRestore<bool> disableListener(
2526 EditSubActionDataRef().mAdjustChangedRangeFromListener);
2527 EditSubActionDataRef().mAdjustChangedRangeFromListener = false;
2529 // TODO: We don't need AutoTransactionsConserveSelection here in the normal
2530 // cases, but removing this may cause the behavior with the legacy
2531 // mutation event listeners. We should try to delete this in a bug.
2532 AutoTransactionsConserveSelection dontChangeMySelection(*this);
2534 EditorDOMPoint pointToPutCaret;
2536 AutoTrackDOMPoint trackingInsertingPosition(RangeUpdaterRef(),
2537 &pointToInsert);
2538 Result<InsertTextResult, nsresult> insertTextResult =
2539 InsertTextWithTransaction(*document, u"\n"_ns, pointToInsert,
2540 InsertTextTo::ExistingTextNodeIfAvailable);
2541 if (MOZ_UNLIKELY(insertTextResult.isErr())) {
2542 NS_WARNING("HTMLEditor::InsertTextWithTransaction() failed");
2543 return insertTextResult.propagateErr();
2545 // Ignore the caret suggestion because of `dontChangeMySelection` above.
2546 insertTextResult.inspect().IgnoreCaretPointSuggestion();
2547 pointToPutCaret = insertTextResult.inspect().Handled()
2548 ? insertTextResult.unwrap()
2549 .EndOfInsertedTextRef()
2550 .To<EditorDOMPoint>()
2551 : pointToInsert;
2554 // Insert a padding <br> element at the end of the block element if there is
2555 // no content between the inserted linefeed and the following block boundary
2556 // to make sure that the last line is visible.
2557 // XXX Blink/WebKit inserts another linefeed character in this case. However,
2558 // for doing it, we need more work, e.g., updating serializer, deleting
2559 // unnecessary padding <br> element at modifying the last line.
2560 if (pointToPutCaret.IsInContentNode() && pointToPutCaret.IsEndOfContainer()) {
2561 WSRunScanner wsScannerAtCaret(&aEditingHost, pointToPutCaret,
2562 BlockInlineCheck::UseComputedDisplayStyle);
2563 if (wsScannerAtCaret.StartsFromPreformattedLineBreak() &&
2564 (wsScannerAtCaret.EndsByBlockBoundary() ||
2565 wsScannerAtCaret.EndsByInlineEditingHostBoundary()) &&
2566 HTMLEditUtils::CanNodeContain(*wsScannerAtCaret.GetEndReasonContent(),
2567 *nsGkAtoms::br)) {
2568 AutoTrackDOMPoint trackingInsertedPosition(RangeUpdaterRef(),
2569 &pointToInsert);
2570 AutoTrackDOMPoint trackingNewCaretPosition(RangeUpdaterRef(),
2571 &pointToPutCaret);
2572 Result<CreateElementResult, nsresult> insertBRElementResult =
2573 InsertBRElement(WithTransaction::Yes, pointToPutCaret);
2574 if (MOZ_UNLIKELY(insertBRElementResult.isErr())) {
2575 NS_WARNING("HTMLEditor::InsertBRElement(WithTransaction::Yes) failed");
2576 return insertBRElementResult.propagateErr();
2578 // We're tracking next caret position with newCaretPosition. Therefore,
2579 // we don't need to update selection here.
2580 insertBRElementResult.inspect().IgnoreCaretPointSuggestion();
2581 MOZ_ASSERT(insertBRElementResult.inspect().GetNewNode());
2585 // manually update the doc changed range so that
2586 // OnEndHandlingTopLevelEditSubActionInternal will clean up the correct
2587 // portion of the document.
2588 MOZ_ASSERT(pointToPutCaret.IsSet());
2589 if (NS_WARN_IF(!pointToPutCaret.IsSet())) {
2590 // XXX Here is odd. We did mChangedRange->SetStartAndEnd(pointToInsert,
2591 // pointToPutCaret), but it always fails because of the latter is unset.
2592 // Therefore, always returning NS_ERROR_FAILURE from here is the
2593 // traditional behavior...
2594 // TODO: Stop updating the interline position of Selection with fixing here
2595 // and returning expected point.
2596 DebugOnly<nsresult> rvIgnored =
2597 SelectionRef().SetInterlinePosition(InterlinePosition::EndOfLine);
2598 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
2599 "Selection::SetInterlinePosition(InterlinePosition::"
2600 "EndOfLine) failed, but ignored");
2601 if (NS_FAILED(TopLevelEditSubActionDataRef().mChangedRange->CollapseTo(
2602 pointToInsert))) {
2603 NS_WARNING("nsRange::CollapseTo() failed");
2604 return Err(NS_ERROR_FAILURE);
2606 NS_WARNING(
2607 "We always return NS_ERROR_FAILURE here because of a failure of "
2608 "updating mChangedRange");
2609 return Err(NS_ERROR_FAILURE);
2612 if (NS_FAILED(TopLevelEditSubActionDataRef().mChangedRange->SetStartAndEnd(
2613 pointToInsert.ToRawRangeBoundary(),
2614 pointToPutCaret.ToRawRangeBoundary()))) {
2615 NS_WARNING("nsRange::SetStartAndEnd() failed");
2616 return Err(NS_ERROR_FAILURE);
2619 pointToPutCaret.SetInterlinePosition(InterlinePosition::EndOfLine);
2620 return pointToPutCaret;
2623 Result<EditorDOMPoint, nsresult>
2624 HTMLEditor::HandleInsertParagraphInMailCiteElement(
2625 Element& aMailCiteElement, const EditorDOMPoint& aPointToSplit,
2626 const Element& aEditingHost) {
2627 MOZ_ASSERT(IsEditActionDataAvailable());
2628 MOZ_ASSERT(aPointToSplit.IsSet());
2629 NS_ASSERTION(!HTMLEditUtils::IsEmptyNode(
2630 aMailCiteElement,
2631 {EmptyCheckOption::TreatNonEditableContentAsInvisible}),
2632 "The mail-cite element will be deleted, does it expected result "
2633 "for you?");
2635 auto splitCiteElementResult =
2636 [&]() MOZ_CAN_RUN_SCRIPT -> Result<SplitNodeResult, nsresult> {
2637 EditorDOMPoint pointToSplit(aPointToSplit);
2639 // If our selection is just before a break, nudge it to be just after
2640 // it. This does two things for us. It saves us the trouble of having
2641 // to add a break here ourselves to preserve the "blockness" of the
2642 // inline span mailquote (in the inline case), and : it means the break
2643 // won't end up making an empty line that happens to be inside a
2644 // mailquote (in either inline or block case). The latter can confuse a
2645 // user if they click there and start typing, because being in the
2646 // mailquote may affect wrapping behavior, or font color, etc.
2647 const WSScanResult forwardScanFromPointToSplitResult =
2648 WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary(
2649 &aEditingHost, pointToSplit, BlockInlineCheck::UseHTMLDefaultStyle);
2650 if (forwardScanFromPointToSplitResult.Failed()) {
2651 return Err(NS_ERROR_FAILURE);
2653 // If selection start point is before a break and it's inside the
2654 // mailquote, let's split it after the visible node.
2655 if (forwardScanFromPointToSplitResult.ReachedBRElement() &&
2656 forwardScanFromPointToSplitResult.BRElementPtr() != &aMailCiteElement &&
2657 aMailCiteElement.Contains(
2658 forwardScanFromPointToSplitResult.BRElementPtr())) {
2659 pointToSplit = forwardScanFromPointToSplitResult
2660 .PointAfterReachedContent<EditorDOMPoint>();
2663 if (NS_WARN_IF(!pointToSplit.IsInContentNode())) {
2664 return Err(NS_ERROR_FAILURE);
2667 Result<SplitNodeResult, nsresult> splitResult =
2668 SplitNodeDeepWithTransaction(aMailCiteElement, pointToSplit,
2669 SplitAtEdges::eDoNotCreateEmptyContainer);
2670 if (MOZ_UNLIKELY(splitResult.isErr())) {
2671 NS_WARNING(
2672 "HTMLEditor::SplitNodeDeepWithTransaction(aMailCiteElement, "
2673 "SplitAtEdges::eDoNotCreateEmptyContainer) failed");
2674 return splitResult;
2676 nsresult rv = splitResult.inspect().SuggestCaretPointTo(
2677 *this, {SuggestCaret::OnlyIfHasSuggestion,
2678 SuggestCaret::OnlyIfTransactionsAllowedToDoIt});
2679 if (NS_FAILED(rv)) {
2680 NS_WARNING("SplitNodeResult::SuggestCaretPointTo() failed");
2681 return Err(rv);
2683 return splitResult;
2684 }();
2685 if (MOZ_UNLIKELY(splitCiteElementResult.isErr())) {
2686 NS_WARNING("Failed to split a mail-cite element");
2687 return splitCiteElementResult.propagateErr();
2689 SplitNodeResult unwrappedSplitCiteElementResult =
2690 splitCiteElementResult.unwrap();
2691 // When adding caret suggestion to SplitNodeResult, here didn't change
2692 // selection so that just ignore it.
2693 unwrappedSplitCiteElementResult.IgnoreCaretPointSuggestion();
2695 // Add an invisible <br> to the end of left cite node if it was a <span> of
2696 // style="display: block". This is important, since when serializing the cite
2697 // to plain text, the span which caused the visual break is discarded. So the
2698 // added <br> will guarantee that the serializer will insert a break where the
2699 // user saw one.
2700 // FYI: unwrappedSplitCiteElementResult grabs the previous node and the next
2701 // node with nsCOMPtr or EditorDOMPoint. So, it's safe to access
2702 // leftCiteElement and rightCiteElement even after changing the DOM tree
2703 // and/or selection even though it's raw pointer.
2704 auto* const leftCiteElement =
2705 unwrappedSplitCiteElementResult.GetPreviousContentAs<Element>();
2706 auto* const rightCiteElement =
2707 unwrappedSplitCiteElementResult.GetNextContentAs<Element>();
2708 if (leftCiteElement && leftCiteElement->IsHTMLElement(nsGkAtoms::span) &&
2709 // XXX Oh, this depends on layout information of new element, and it's
2710 // created by the hacky flush in DoSplitNode(). So we need to
2711 // redesign around this for bug 1710784.
2712 leftCiteElement->GetPrimaryFrame() &&
2713 leftCiteElement->GetPrimaryFrame()->IsBlockFrameOrSubclass()) {
2714 nsIContent* lastChild = leftCiteElement->GetLastChild();
2715 if (lastChild && !lastChild->IsHTMLElement(nsGkAtoms::br)) {
2716 Result<CreateElementResult, nsresult> insertInvisibleBRElementResult =
2717 InsertBRElement(WithTransaction::Yes,
2718 EditorDOMPoint::AtEndOf(*leftCiteElement));
2719 if (MOZ_UNLIKELY(insertInvisibleBRElementResult.isErr())) {
2720 NS_WARNING("HTMLEditor::InsertBRElement(WithTransaction::Yes) failed");
2721 return insertInvisibleBRElementResult.propagateErr();
2723 // We don't need to update selection here because we'll do another
2724 // InsertBRElement call soon.
2725 insertInvisibleBRElementResult.inspect().IgnoreCaretPointSuggestion();
2726 MOZ_ASSERT(insertInvisibleBRElementResult.inspect().GetNewNode());
2730 // In most cases, <br> should be inserted after current cite. However, if
2731 // left cite hasn't been created because the split point was start of the
2732 // cite node, <br> should be inserted before the current cite.
2733 Result<CreateElementResult, nsresult> insertBRElementResult = InsertBRElement(
2734 WithTransaction::Yes,
2735 unwrappedSplitCiteElementResult.AtSplitPoint<EditorDOMPoint>());
2736 if (MOZ_UNLIKELY(insertBRElementResult.isErr())) {
2737 NS_WARNING("HTMLEditor::InsertBRElement(WithTransaction::Yes) failed");
2738 return Err(insertBRElementResult.unwrapErr());
2740 CreateElementResult unwrappedInsertBRElementResult =
2741 insertBRElementResult.unwrap();
2742 // We'll return with suggesting caret position. Therefore, we don't need
2743 // to update selection here.
2744 unwrappedInsertBRElementResult.IgnoreCaretPointSuggestion();
2745 MOZ_ASSERT(unwrappedInsertBRElementResult.GetNewNode());
2747 // if aMailCiteElement wasn't a block, we might also want another break before
2748 // it. We need to examine the content both before the br we just added and
2749 // also just after it. If we don't have another br or block boundary
2750 // adjacent, then we will need a 2nd br added to achieve blank line that user
2751 // expects.
2752 if (HTMLEditUtils::IsInlineContent(
2753 aMailCiteElement, BlockInlineCheck::UseComputedDisplayStyle)) {
2754 nsresult rvOfInsertingBRElement = [&]() MOZ_CAN_RUN_SCRIPT {
2755 EditorDOMPoint pointToCreateNewBRElement(
2756 unwrappedInsertBRElementResult.GetNewNode());
2758 // XXX Cannot we replace this complicated check with just a call of
2759 // HTMLEditUtils::IsVisibleBRElement with
2760 // resultOfInsertingBRElement.inspect()?
2761 const WSScanResult backwardScanFromPointToCreateNewBRElementResult =
2762 WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary(
2763 &aEditingHost, pointToCreateNewBRElement,
2764 BlockInlineCheck::UseComputedDisplayStyle);
2765 if (MOZ_UNLIKELY(
2766 backwardScanFromPointToCreateNewBRElementResult.Failed())) {
2767 NS_WARNING(
2768 "WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary() "
2769 "failed");
2770 return NS_ERROR_FAILURE;
2772 if (!backwardScanFromPointToCreateNewBRElementResult
2773 .InVisibleOrCollapsibleCharacters() &&
2774 !backwardScanFromPointToCreateNewBRElementResult
2775 .ReachedSpecialContent()) {
2776 return NS_SUCCESS_DOM_NO_OPERATION;
2778 const WSScanResult forwardScanFromPointAfterNewBRElementResult =
2779 WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary(
2780 &aEditingHost,
2781 EditorRawDOMPoint::After(pointToCreateNewBRElement),
2782 BlockInlineCheck::UseComputedDisplayStyle);
2783 if (MOZ_UNLIKELY(forwardScanFromPointAfterNewBRElementResult.Failed())) {
2784 NS_WARNING("WSRunScanner::ScanNextVisibleNodeOrBlockBoundary() failed");
2785 return NS_ERROR_FAILURE;
2787 if (!forwardScanFromPointAfterNewBRElementResult
2788 .InVisibleOrCollapsibleCharacters() &&
2789 !forwardScanFromPointAfterNewBRElementResult
2790 .ReachedSpecialContent() &&
2791 // In case we're at the very end.
2792 !forwardScanFromPointAfterNewBRElementResult
2793 .ReachedCurrentBlockBoundary()) {
2794 return NS_SUCCESS_DOM_NO_OPERATION;
2796 Result<CreateElementResult, nsresult> insertBRElementResult =
2797 InsertBRElement(WithTransaction::Yes, pointToCreateNewBRElement);
2798 if (MOZ_UNLIKELY(insertBRElementResult.isErr())) {
2799 NS_WARNING("HTMLEditor::InsertBRElement(WithTransaction::Yes) failed");
2800 return insertBRElementResult.unwrapErr();
2802 insertBRElementResult.inspect().IgnoreCaretPointSuggestion();
2803 MOZ_ASSERT(insertBRElementResult.inspect().GetNewNode());
2804 return NS_OK;
2805 }();
2807 if (NS_FAILED(rvOfInsertingBRElement)) {
2808 NS_WARNING(
2809 "Failed to insert additional <br> element before the inline right "
2810 "mail-cite element");
2811 return Err(rvOfInsertingBRElement);
2815 if (leftCiteElement &&
2816 HTMLEditUtils::IsEmptyNode(
2817 *leftCiteElement,
2818 {EmptyCheckOption::TreatNonEditableContentAsInvisible})) {
2819 // MOZ_KnownLive(leftCiteElement) because it's grabbed by
2820 // unwrappedSplitCiteElementResult.
2821 nsresult rv = DeleteNodeWithTransaction(MOZ_KnownLive(*leftCiteElement));
2822 if (NS_FAILED(rv)) {
2823 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
2824 return Err(rv);
2828 if (rightCiteElement &&
2829 HTMLEditUtils::IsEmptyNode(
2830 *rightCiteElement,
2831 {EmptyCheckOption::TreatNonEditableContentAsInvisible})) {
2832 // MOZ_KnownLive(rightCiteElement) because it's grabbed by
2833 // unwrappedSplitCiteElementResult.
2834 nsresult rv = DeleteNodeWithTransaction(MOZ_KnownLive(*rightCiteElement));
2835 if (NS_FAILED(rv)) {
2836 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
2837 return Err(rv);
2841 if (MOZ_UNLIKELY(!unwrappedInsertBRElementResult.GetNewNode()->GetParent())) {
2842 NS_WARNING("Inserted <br> shouldn't become an orphan node");
2843 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
2845 return EditorDOMPoint(unwrappedInsertBRElementResult.GetNewNode());
2848 HTMLEditor::CharPointData
2849 HTMLEditor::GetPreviousCharPointDataForNormalizingWhiteSpaces(
2850 const EditorDOMPointInText& aPoint) const {
2851 MOZ_ASSERT(aPoint.IsSetAndValid());
2853 if (!aPoint.IsStartOfContainer()) {
2854 return CharPointData::InSameTextNode(
2855 HTMLEditor::GetPreviousCharPointType(aPoint));
2857 const auto previousCharPoint =
2858 WSRunScanner::GetPreviousEditableCharPoint<EditorRawDOMPointInText>(
2859 ComputeEditingHost(), aPoint,
2860 BlockInlineCheck::UseComputedDisplayStyle);
2861 if (!previousCharPoint.IsSet()) {
2862 return CharPointData::InDifferentTextNode(CharPointType::TextEnd);
2864 return CharPointData::InDifferentTextNode(
2865 HTMLEditor::GetCharPointType(previousCharPoint));
2868 HTMLEditor::CharPointData
2869 HTMLEditor::GetInclusiveNextCharPointDataForNormalizingWhiteSpaces(
2870 const EditorDOMPointInText& aPoint) const {
2871 MOZ_ASSERT(aPoint.IsSetAndValid());
2873 if (!aPoint.IsEndOfContainer()) {
2874 return CharPointData::InSameTextNode(HTMLEditor::GetCharPointType(aPoint));
2876 const auto nextCharPoint =
2877 WSRunScanner::GetInclusiveNextEditableCharPoint<EditorRawDOMPointInText>(
2878 ComputeEditingHost(), aPoint,
2879 BlockInlineCheck::UseComputedDisplayStyle);
2880 if (!nextCharPoint.IsSet()) {
2881 return CharPointData::InDifferentTextNode(CharPointType::TextEnd);
2883 return CharPointData::InDifferentTextNode(
2884 HTMLEditor::GetCharPointType(nextCharPoint));
2887 // static
2888 void HTMLEditor::GenerateWhiteSpaceSequence(
2889 nsAString& aResult, uint32_t aLength,
2890 const CharPointData& aPreviousCharPointData,
2891 const CharPointData& aNextCharPointData) {
2892 MOZ_ASSERT(aResult.IsEmpty());
2893 MOZ_ASSERT(aLength);
2894 // For now, this method does not assume that result will be append to
2895 // white-space sequence in the text node.
2896 MOZ_ASSERT(aPreviousCharPointData.AcrossTextNodeBoundary() ||
2897 !aPreviousCharPointData.IsCollapsibleWhiteSpace());
2898 // For now, this method does not assume that the result will be inserted
2899 // into white-space sequence nor start of white-space sequence.
2900 MOZ_ASSERT(aNextCharPointData.AcrossTextNodeBoundary() ||
2901 !aNextCharPointData.IsCollapsibleWhiteSpace());
2903 if (aLength == 1) {
2904 // Even if previous/next char is in different text node, we should put
2905 // an ASCII white-space between visible characters.
2906 // XXX This means that this does not allow to put an NBSP in HTML editor
2907 // without preformatted style. However, Chrome has same issue too.
2908 if (aPreviousCharPointData.Type() == CharPointType::VisibleChar &&
2909 aNextCharPointData.Type() == CharPointType::VisibleChar) {
2910 aResult.Assign(HTMLEditUtils::kSpace);
2911 return;
2913 // If it's start or end of text, put an NBSP.
2914 if (aPreviousCharPointData.Type() == CharPointType::TextEnd ||
2915 aNextCharPointData.Type() == CharPointType::TextEnd) {
2916 aResult.Assign(HTMLEditUtils::kNBSP);
2917 return;
2919 // If the character is next to a preformatted linefeed, we need to put
2920 // an NBSP for avoiding collapsed into the linefeed.
2921 if (aPreviousCharPointData.Type() == CharPointType::PreformattedLineBreak ||
2922 aNextCharPointData.Type() == CharPointType::PreformattedLineBreak) {
2923 aResult.Assign(HTMLEditUtils::kNBSP);
2924 return;
2926 // Now, the white-space will be inserted to a white-space sequence, but not
2927 // end of text. We can put an ASCII white-space only when both sides are
2928 // not ASCII white-spaces.
2929 aResult.Assign(
2930 aPreviousCharPointData.Type() == CharPointType::ASCIIWhiteSpace ||
2931 aNextCharPointData.Type() == CharPointType::ASCIIWhiteSpace
2932 ? HTMLEditUtils::kNBSP
2933 : HTMLEditUtils::kSpace);
2934 return;
2937 // Generate pairs of NBSP and ASCII white-space.
2938 aResult.SetLength(aLength);
2939 bool appendNBSP = true; // Basically, starts with an NBSP.
2940 char16_t* lastChar = aResult.EndWriting() - 1;
2941 for (char16_t* iter = aResult.BeginWriting(); iter != lastChar; iter++) {
2942 *iter = appendNBSP ? HTMLEditUtils::kNBSP : HTMLEditUtils::kSpace;
2943 appendNBSP = !appendNBSP;
2946 // If the final one is expected to an NBSP, we can put an NBSP simply.
2947 if (appendNBSP) {
2948 *lastChar = HTMLEditUtils::kNBSP;
2949 return;
2952 // If next char point is end of text node, an ASCII white-space or
2953 // preformatted linefeed, we need to put an NBSP.
2954 *lastChar =
2955 aNextCharPointData.AcrossTextNodeBoundary() ||
2956 aNextCharPointData.Type() == CharPointType::ASCIIWhiteSpace ||
2957 aNextCharPointData.Type() == CharPointType::PreformattedLineBreak
2958 ? HTMLEditUtils::kNBSP
2959 : HTMLEditUtils::kSpace;
2962 void HTMLEditor::ExtendRangeToDeleteWithNormalizingWhiteSpaces(
2963 EditorDOMPointInText& aStartToDelete, EditorDOMPointInText& aEndToDelete,
2964 nsAString& aNormalizedWhiteSpacesInStartNode,
2965 nsAString& aNormalizedWhiteSpacesInEndNode) const {
2966 MOZ_ASSERT(aStartToDelete.IsSetAndValid());
2967 MOZ_ASSERT(aEndToDelete.IsSetAndValid());
2968 MOZ_ASSERT(aStartToDelete.EqualsOrIsBefore(aEndToDelete));
2969 MOZ_ASSERT(aNormalizedWhiteSpacesInStartNode.IsEmpty());
2970 MOZ_ASSERT(aNormalizedWhiteSpacesInEndNode.IsEmpty());
2972 // First, check whether there is surrounding white-spaces or not, and if there
2973 // are, check whether they are collapsible or not. Note that we shouldn't
2974 // touch white-spaces in different text nodes for performance, but we need
2975 // adjacent text node's first or last character information in some cases.
2976 Element* editingHost = ComputeEditingHost();
2977 const EditorDOMPointInText precedingCharPoint =
2978 WSRunScanner::GetPreviousEditableCharPoint(
2979 editingHost, aStartToDelete,
2980 BlockInlineCheck::UseComputedDisplayStyle);
2981 const EditorDOMPointInText followingCharPoint =
2982 WSRunScanner::GetInclusiveNextEditableCharPoint(
2983 editingHost, aEndToDelete, BlockInlineCheck::UseComputedDisplayStyle);
2984 // Blink-compat: Normalize white-spaces in first node only when not removing
2985 // its last character or no text nodes follow the first node.
2986 // If removing last character of first node and there are
2987 // following text nodes, white-spaces in following text node are
2988 // normalized instead.
2989 const bool removingLastCharOfStartNode =
2990 aStartToDelete.ContainerAs<Text>() != aEndToDelete.ContainerAs<Text>() ||
2991 (aEndToDelete.IsEndOfContainer() && followingCharPoint.IsSet());
2992 const bool maybeNormalizePrecedingWhiteSpaces =
2993 !removingLastCharOfStartNode && precedingCharPoint.IsSet() &&
2994 !precedingCharPoint.IsEndOfContainer() &&
2995 precedingCharPoint.ContainerAs<Text>() ==
2996 aStartToDelete.ContainerAs<Text>() &&
2997 precedingCharPoint.IsCharCollapsibleASCIISpaceOrNBSP();
2998 const bool maybeNormalizeFollowingWhiteSpaces =
2999 followingCharPoint.IsSet() && !followingCharPoint.IsEndOfContainer() &&
3000 (followingCharPoint.ContainerAs<Text>() ==
3001 aEndToDelete.ContainerAs<Text>() ||
3002 removingLastCharOfStartNode) &&
3003 followingCharPoint.IsCharCollapsibleASCIISpaceOrNBSP();
3005 if (!maybeNormalizePrecedingWhiteSpaces &&
3006 !maybeNormalizeFollowingWhiteSpaces) {
3007 return; // There are no white-spaces.
3010 // Next, consider the range to normalize.
3011 EditorDOMPointInText startToNormalize, endToNormalize;
3012 if (maybeNormalizePrecedingWhiteSpaces) {
3013 Maybe<uint32_t> previousCharOffsetOfWhiteSpaces =
3014 HTMLEditUtils::GetPreviousNonCollapsibleCharOffset(
3015 precedingCharPoint, {WalkTextOption::TreatNBSPsCollapsible});
3016 startToNormalize.Set(precedingCharPoint.ContainerAs<Text>(),
3017 previousCharOffsetOfWhiteSpaces.isSome()
3018 ? previousCharOffsetOfWhiteSpaces.value() + 1
3019 : 0);
3020 MOZ_ASSERT(!startToNormalize.IsEndOfContainer());
3022 if (maybeNormalizeFollowingWhiteSpaces) {
3023 Maybe<uint32_t> nextCharOffsetOfWhiteSpaces =
3024 HTMLEditUtils::GetInclusiveNextNonCollapsibleCharOffset(
3025 followingCharPoint, {WalkTextOption::TreatNBSPsCollapsible});
3026 if (nextCharOffsetOfWhiteSpaces.isSome()) {
3027 endToNormalize.Set(followingCharPoint.ContainerAs<Text>(),
3028 nextCharOffsetOfWhiteSpaces.value());
3029 } else {
3030 endToNormalize.SetToEndOf(followingCharPoint.ContainerAs<Text>());
3032 MOZ_ASSERT(!endToNormalize.IsStartOfContainer());
3035 // Next, retrieve surrounding information of white-space sequence.
3036 // If we're removing first text node's last character, we need to
3037 // normalize white-spaces starts from another text node. In this case,
3038 // we need to lie for avoiding assertion in GenerateWhiteSpaceSequence().
3039 CharPointData previousCharPointData =
3040 removingLastCharOfStartNode
3041 ? CharPointData::InDifferentTextNode(CharPointType::TextEnd)
3042 : GetPreviousCharPointDataForNormalizingWhiteSpaces(
3043 startToNormalize.IsSet() ? startToNormalize : aStartToDelete);
3044 CharPointData nextCharPointData =
3045 GetInclusiveNextCharPointDataForNormalizingWhiteSpaces(
3046 endToNormalize.IsSet() ? endToNormalize : aEndToDelete);
3048 // Next, compute number of white-spaces in start/end node.
3049 uint32_t lengthInStartNode = 0, lengthInEndNode = 0;
3050 if (startToNormalize.IsSet()) {
3051 MOZ_ASSERT(startToNormalize.ContainerAs<Text>() ==
3052 aStartToDelete.ContainerAs<Text>());
3053 lengthInStartNode = aStartToDelete.Offset() - startToNormalize.Offset();
3054 MOZ_ASSERT(lengthInStartNode);
3056 if (endToNormalize.IsSet()) {
3057 lengthInEndNode =
3058 endToNormalize.ContainerAs<Text>() == aEndToDelete.ContainerAs<Text>()
3059 ? endToNormalize.Offset() - aEndToDelete.Offset()
3060 : endToNormalize.Offset();
3061 MOZ_ASSERT(lengthInEndNode);
3062 // If we normalize white-spaces in a text node, we can replace all of them
3063 // with one ReplaceTextTransaction.
3064 if (endToNormalize.ContainerAs<Text>() ==
3065 aStartToDelete.ContainerAs<Text>()) {
3066 lengthInStartNode += lengthInEndNode;
3067 lengthInEndNode = 0;
3071 MOZ_ASSERT(lengthInStartNode + lengthInEndNode);
3073 // Next, generate normalized white-spaces.
3074 if (!lengthInEndNode) {
3075 HTMLEditor::GenerateWhiteSpaceSequence(
3076 aNormalizedWhiteSpacesInStartNode, lengthInStartNode,
3077 previousCharPointData, nextCharPointData);
3078 } else if (!lengthInStartNode) {
3079 HTMLEditor::GenerateWhiteSpaceSequence(
3080 aNormalizedWhiteSpacesInEndNode, lengthInEndNode, previousCharPointData,
3081 nextCharPointData);
3082 } else {
3083 // For making `GenerateWhiteSpaceSequence()` simpler, we should create
3084 // whole white-space sequence first, then, copy to the out params.
3085 nsAutoString whiteSpaces;
3086 HTMLEditor::GenerateWhiteSpaceSequence(
3087 whiteSpaces, lengthInStartNode + lengthInEndNode, previousCharPointData,
3088 nextCharPointData);
3089 aNormalizedWhiteSpacesInStartNode =
3090 Substring(whiteSpaces, 0, lengthInStartNode);
3091 aNormalizedWhiteSpacesInEndNode = Substring(whiteSpaces, lengthInStartNode);
3092 MOZ_ASSERT(aNormalizedWhiteSpacesInEndNode.Length() == lengthInEndNode);
3095 // TODO: Shrink the replacing range and string as far as possible because
3096 // this may run a lot, i.e., HTMLEditor creates ReplaceTextTransaction
3097 // a lot for normalizing white-spaces. Then, each transaction shouldn't
3098 // have all white-spaces every time because once it's normalized, we
3099 // don't need to normalize all of the sequence again, but currently
3100 // we do.
3102 // Finally, extend the range.
3103 if (startToNormalize.IsSet()) {
3104 aStartToDelete = startToNormalize;
3106 if (endToNormalize.IsSet()) {
3107 aEndToDelete = endToNormalize;
3111 Result<CaretPoint, nsresult>
3112 HTMLEditor::DeleteTextAndNormalizeSurroundingWhiteSpaces(
3113 const EditorDOMPointInText& aStartToDelete,
3114 const EditorDOMPointInText& aEndToDelete,
3115 TreatEmptyTextNodes aTreatEmptyTextNodes,
3116 DeleteDirection aDeleteDirection) {
3117 MOZ_ASSERT(aStartToDelete.IsSetAndValid());
3118 MOZ_ASSERT(aEndToDelete.IsSetAndValid());
3119 MOZ_ASSERT(aStartToDelete.EqualsOrIsBefore(aEndToDelete));
3121 // Use nsString for these replacing string because we should avoid to copy
3122 // the buffer from auto storange to ReplaceTextTransaction.
3123 nsString normalizedWhiteSpacesInFirstNode, normalizedWhiteSpacesInLastNode;
3125 // First, check whether we need to normalize white-spaces after deleting
3126 // the given range.
3127 EditorDOMPointInText startToDelete(aStartToDelete);
3128 EditorDOMPointInText endToDelete(aEndToDelete);
3129 ExtendRangeToDeleteWithNormalizingWhiteSpaces(
3130 startToDelete, endToDelete, normalizedWhiteSpacesInFirstNode,
3131 normalizedWhiteSpacesInLastNode);
3133 // If extended range is still collapsed, i.e., the caller just wants to
3134 // normalize white-space sequence, but there is no white-spaces which need to
3135 // be replaced, we need to do nothing here.
3136 if (startToDelete == endToDelete) {
3137 return CaretPoint(aStartToDelete.To<EditorDOMPoint>());
3140 // Note that the container text node of startToDelete may be removed from
3141 // the tree if it becomes empty. Therefore, we need to track the point.
3142 EditorDOMPoint newCaretPosition;
3143 if (aStartToDelete.ContainerAs<Text>() == aEndToDelete.ContainerAs<Text>()) {
3144 newCaretPosition = aEndToDelete.To<EditorDOMPoint>();
3145 } else if (aDeleteDirection == DeleteDirection::Forward) {
3146 newCaretPosition.SetToEndOf(aStartToDelete.ContainerAs<Text>());
3147 } else {
3148 newCaretPosition.Set(aEndToDelete.ContainerAs<Text>(), 0u);
3151 // Then, modify the text nodes in the range.
3152 while (true) {
3153 AutoTrackDOMPoint trackingNewCaretPosition(RangeUpdaterRef(),
3154 &newCaretPosition);
3155 // Use ReplaceTextTransaction if we need to normalize white-spaces in
3156 // the first text node.
3157 if (!normalizedWhiteSpacesInFirstNode.IsEmpty()) {
3158 EditorDOMPoint trackingEndToDelete(endToDelete.ContainerAs<Text>(),
3159 endToDelete.Offset());
3161 AutoTrackDOMPoint trackEndToDelete(RangeUpdaterRef(),
3162 &trackingEndToDelete);
3163 uint32_t lengthToReplaceInFirstTextNode =
3164 startToDelete.ContainerAs<Text>() ==
3165 trackingEndToDelete.ContainerAs<Text>()
3166 ? trackingEndToDelete.Offset() - startToDelete.Offset()
3167 : startToDelete.ContainerAs<Text>()->TextLength() -
3168 startToDelete.Offset();
3169 Result<InsertTextResult, nsresult> replaceTextResult =
3170 ReplaceTextWithTransaction(
3171 MOZ_KnownLive(*startToDelete.ContainerAs<Text>()),
3172 startToDelete.Offset(), lengthToReplaceInFirstTextNode,
3173 normalizedWhiteSpacesInFirstNode);
3174 if (MOZ_UNLIKELY(replaceTextResult.isErr())) {
3175 NS_WARNING("HTMLEditor::ReplaceTextWithTransaction() failed");
3176 return replaceTextResult.propagateErr();
3178 // We'll return computed caret point, newCaretPosition, below.
3179 replaceTextResult.unwrap().IgnoreCaretPointSuggestion();
3180 if (startToDelete.ContainerAs<Text>() ==
3181 trackingEndToDelete.ContainerAs<Text>()) {
3182 MOZ_ASSERT(normalizedWhiteSpacesInLastNode.IsEmpty());
3183 break; // There is no more text which we need to delete.
3186 if (MayHaveMutationEventListeners(
3187 NS_EVENT_BITS_MUTATION_CHARACTERDATAMODIFIED) &&
3188 (NS_WARN_IF(!trackingEndToDelete.IsSetAndValid()) ||
3189 NS_WARN_IF(!trackingEndToDelete.IsInTextNode()))) {
3190 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
3192 MOZ_ASSERT(trackingEndToDelete.IsInTextNode());
3193 endToDelete.Set(trackingEndToDelete.ContainerAs<Text>(),
3194 trackingEndToDelete.Offset());
3195 // If the remaining range was modified by mutation event listener,
3196 // we should stop handling the deletion.
3197 startToDelete =
3198 EditorDOMPointInText::AtEndOf(*startToDelete.ContainerAs<Text>());
3199 if (MayHaveMutationEventListeners(
3200 NS_EVENT_BITS_MUTATION_CHARACTERDATAMODIFIED) &&
3201 NS_WARN_IF(!startToDelete.IsBefore(endToDelete))) {
3202 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
3205 // Delete ASCII whiteSpaces in the range simpley if there are some text
3206 // nodes which we don't need to replace their text.
3207 if (normalizedWhiteSpacesInLastNode.IsEmpty() ||
3208 startToDelete.ContainerAs<Text>() != endToDelete.ContainerAs<Text>()) {
3209 // If we need to replace text in the last text node, we should
3210 // delete text before its previous text node.
3211 EditorDOMPointInText endToDeleteExceptReplaceRange =
3212 normalizedWhiteSpacesInLastNode.IsEmpty()
3213 ? endToDelete
3214 : EditorDOMPointInText(endToDelete.ContainerAs<Text>(), 0);
3215 if (startToDelete != endToDeleteExceptReplaceRange) {
3216 Result<CaretPoint, nsresult> caretPointOrError =
3217 DeleteTextAndTextNodesWithTransaction(startToDelete,
3218 endToDeleteExceptReplaceRange,
3219 aTreatEmptyTextNodes);
3220 if (MOZ_UNLIKELY(caretPointOrError.isErr())) {
3221 NS_WARNING(
3222 "HTMLEditor::DeleteTextAndTextNodesWithTransaction() failed");
3223 return caretPointOrError.propagateErr();
3225 nsresult rv = caretPointOrError.unwrap().SuggestCaretPointTo(
3226 *this, {SuggestCaret::OnlyIfHasSuggestion,
3227 SuggestCaret::OnlyIfTransactionsAllowedToDoIt,
3228 SuggestCaret::AndIgnoreTrivialError});
3229 if (NS_FAILED(rv)) {
3230 NS_WARNING("CaretPoint::SuggestCaretPointTo() failed");
3231 return Err(rv);
3233 NS_WARNING_ASSERTION(
3234 rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
3235 "CaretPoint::SuggestCaretPointTo() failed, but ignored");
3236 if (normalizedWhiteSpacesInLastNode.IsEmpty()) {
3237 break; // There is no more text which we need to delete.
3239 if (MayHaveMutationEventListeners(
3240 NS_EVENT_BITS_MUTATION_CHARACTERDATAMODIFIED |
3241 NS_EVENT_BITS_MUTATION_NODEREMOVED |
3242 NS_EVENT_BITS_MUTATION_NODEREMOVEDFROMDOCUMENT |
3243 NS_EVENT_BITS_MUTATION_SUBTREEMODIFIED) &&
3244 (NS_WARN_IF(!endToDeleteExceptReplaceRange.IsSetAndValid()) ||
3245 NS_WARN_IF(!endToDelete.IsSetAndValid()) ||
3246 NS_WARN_IF(endToDelete.IsStartOfContainer()))) {
3247 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
3249 // Then, replace the text in the last text node.
3250 startToDelete = endToDeleteExceptReplaceRange;
3254 // Replace ASCII whiteSpaces in the range and following character in the
3255 // last text node.
3256 MOZ_ASSERT(!normalizedWhiteSpacesInLastNode.IsEmpty());
3257 MOZ_ASSERT(startToDelete.ContainerAs<Text>() ==
3258 endToDelete.ContainerAs<Text>());
3259 Result<InsertTextResult, nsresult> replaceTextResult =
3260 ReplaceTextWithTransaction(
3261 MOZ_KnownLive(*startToDelete.ContainerAs<Text>()),
3262 startToDelete.Offset(),
3263 endToDelete.Offset() - startToDelete.Offset(),
3264 normalizedWhiteSpacesInLastNode);
3265 if (MOZ_UNLIKELY(replaceTextResult.isErr())) {
3266 NS_WARNING("HTMLEditor::ReplaceTextWithTransaction() failed");
3267 return replaceTextResult.propagateErr();
3269 // We'll return computed caret point, newCaretPosition, below.
3270 replaceTextResult.unwrap().IgnoreCaretPointSuggestion();
3271 break;
3274 if (NS_WARN_IF(!newCaretPosition.IsSetAndValid()) ||
3275 NS_WARN_IF(!newCaretPosition.GetContainer()->IsInComposedDoc())) {
3276 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
3279 // Look for leaf node to put caret if we remove some empty inline ancestors
3280 // at new caret position.
3281 if (!newCaretPosition.IsInTextNode()) {
3282 if (const Element* editableBlockElementOrInlineEditingHost =
3283 HTMLEditUtils::GetInclusiveAncestorElement(
3284 *newCaretPosition.ContainerAs<nsIContent>(),
3285 HTMLEditUtils::ClosestEditableBlockElementOrInlineEditingHost,
3286 BlockInlineCheck::UseComputedDisplayStyle)) {
3287 Element* editingHost = ComputeEditingHost();
3288 // Try to put caret next to immediately after previous editable leaf.
3289 nsIContent* previousContent =
3290 HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement(
3291 newCaretPosition, *editableBlockElementOrInlineEditingHost,
3292 {LeafNodeType::LeafNodeOrNonEditableNode},
3293 BlockInlineCheck::UseComputedDisplayStyle, editingHost);
3294 if (previousContent &&
3295 !HTMLEditUtils::IsBlockElement(
3296 *previousContent, BlockInlineCheck::UseComputedDisplayStyle)) {
3297 newCaretPosition =
3298 previousContent->IsText() ||
3299 HTMLEditUtils::IsContainerNode(*previousContent)
3300 ? EditorDOMPoint::AtEndOf(*previousContent)
3301 : EditorDOMPoint::After(*previousContent);
3303 // But if the point is very first of a block element or immediately after
3304 // a child block, look for next editable leaf instead.
3305 else if (nsIContent* nextContent =
3306 HTMLEditUtils::GetNextLeafContentOrNextBlockElement(
3307 newCaretPosition,
3308 *editableBlockElementOrInlineEditingHost,
3309 {LeafNodeType::LeafNodeOrNonEditableNode},
3310 BlockInlineCheck::UseComputedDisplayStyle,
3311 editingHost)) {
3312 newCaretPosition = nextContent->IsText() ||
3313 HTMLEditUtils::IsContainerNode(*nextContent)
3314 ? EditorDOMPoint(nextContent, 0)
3315 : EditorDOMPoint(nextContent);
3320 // For compatibility with Blink, we should move caret to end of previous
3321 // text node if it's direct previous sibling of the first text node in the
3322 // range.
3323 if (newCaretPosition.IsStartOfContainer() &&
3324 newCaretPosition.IsInTextNode() &&
3325 newCaretPosition.GetContainer()->GetPreviousSibling() &&
3326 newCaretPosition.GetContainer()->GetPreviousSibling()->IsText()) {
3327 newCaretPosition.SetToEndOf(
3328 newCaretPosition.GetContainer()->GetPreviousSibling()->AsText());
3332 AutoTrackDOMPoint trackingNewCaretPosition(RangeUpdaterRef(),
3333 &newCaretPosition);
3334 Result<CaretPoint, nsresult> caretPointOrError =
3335 InsertBRElementIfHardLineIsEmptyAndEndsWithBlockBoundary(
3336 newCaretPosition);
3337 if (MOZ_UNLIKELY(caretPointOrError.isErr())) {
3338 NS_WARNING(
3339 "HTMLEditor::"
3340 "InsertBRElementIfHardLineIsEmptyAndEndsWithBlockBoundary() failed");
3341 return caretPointOrError;
3344 if (!newCaretPosition.IsSetAndValid()) {
3345 NS_WARNING("Inserting <br> element caused unexpected DOM tree");
3346 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
3348 return CaretPoint(std::move(newCaretPosition));
3351 Result<CaretPoint, nsresult>
3352 HTMLEditor::InsertBRElementIfHardLineIsEmptyAndEndsWithBlockBoundary(
3353 const EditorDOMPoint& aPointToInsert) {
3354 MOZ_ASSERT(IsEditActionDataAvailable());
3355 MOZ_ASSERT(aPointToInsert.IsSet());
3357 if (!aPointToInsert.IsInContentNode()) {
3358 return CaretPoint(EditorDOMPoint());
3361 // If container of the point is not in a block, we don't need to put a
3362 // `<br>` element here.
3363 if (!HTMLEditUtils::IsBlockElement(
3364 *aPointToInsert.ContainerAs<nsIContent>(),
3365 BlockInlineCheck::UseComputedDisplayStyle)) {
3366 return CaretPoint(EditorDOMPoint());
3369 if (NS_WARN_IF(!HTMLEditUtils::IsSimplyEditableNode(
3370 *aPointToInsert.ContainerAs<nsIContent>()))) {
3371 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
3374 WSRunScanner wsRunScanner(ComputeEditingHost(), aPointToInsert,
3375 BlockInlineCheck::UseComputedDisplayStyle);
3376 // If the point is not start of a hard line, we don't need to put a `<br>`
3377 // element here.
3378 if (!wsRunScanner.StartsFromHardLineBreak() &&
3379 !wsRunScanner.StartsFromInlineEditingHostBoundary()) {
3380 return CaretPoint(EditorDOMPoint());
3382 // If the point is not end of a hard line or the hard line does not end with
3383 // block boundary, we don't need to put a `<br>` element here.
3384 if (!wsRunScanner.EndsByBlockBoundary() &&
3385 !wsRunScanner.EndsByInlineEditingHostBoundary()) {
3386 return CaretPoint(EditorDOMPoint());
3389 // If we cannot insert a `<br>` element here, do nothing.
3390 if (!HTMLEditUtils::CanNodeContain(*aPointToInsert.GetContainer(),
3391 *nsGkAtoms::br)) {
3392 return CaretPoint(EditorDOMPoint());
3395 Result<CreateElementResult, nsresult> insertBRElementResult = InsertBRElement(
3396 WithTransaction::Yes, aPointToInsert, nsIEditor::ePrevious);
3397 if (MOZ_UNLIKELY(insertBRElementResult.isErr())) {
3398 NS_WARNING(
3399 "HTMLEditor::InsertBRElement(WithTransaction::Yes, ePrevious) failed");
3400 return insertBRElementResult.propagateErr();
3402 return CaretPoint(insertBRElementResult.unwrap().UnwrapCaretPoint());
3405 Result<EditActionResult, nsresult>
3406 HTMLEditor::MakeOrChangeListAndListItemAsSubAction(
3407 const nsStaticAtom& aListElementOrListItemElementTagName,
3408 const nsAString& aBulletType,
3409 SelectAllOfCurrentList aSelectAllOfCurrentList) {
3410 MOZ_ASSERT(IsEditActionDataAvailable());
3411 MOZ_ASSERT(&aListElementOrListItemElementTagName == nsGkAtoms::ul ||
3412 &aListElementOrListItemElementTagName == nsGkAtoms::ol ||
3413 &aListElementOrListItemElementTagName == nsGkAtoms::dl ||
3414 &aListElementOrListItemElementTagName == nsGkAtoms::dd ||
3415 &aListElementOrListItemElementTagName == nsGkAtoms::dt);
3417 if (NS_WARN_IF(!mInitSucceeded)) {
3418 return Err(NS_ERROR_NOT_INITIALIZED);
3422 Result<EditActionResult, nsresult> result = CanHandleHTMLEditSubAction();
3423 if (MOZ_UNLIKELY(result.isErr())) {
3424 NS_WARNING("HTMLEditor::CanHandleHTMLEditSubAction() failed");
3425 return result;
3427 if (result.inspect().Canceled()) {
3428 return result;
3432 if (MOZ_UNLIKELY(IsSelectionRangeContainerNotContent())) {
3433 NS_WARNING("Some selection containers are not content node, but ignored");
3434 return EditActionResult::IgnoredResult();
3437 AutoPlaceholderBatch treatAsOneTransaction(
3438 *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
3440 // XXX EditSubAction::eCreateOrChangeDefinitionListItem and
3441 // EditSubAction::eCreateOrChangeList are treated differently in
3442 // HTMLEditor::MaybeSplitElementsAtEveryBRElement(). Only when
3443 // EditSubAction::eCreateOrChangeList, it splits inline nodes.
3444 // Currently, it shouldn't be done when we called for formatting
3445 // `<dd>` or `<dt>` by
3446 // HTMLEditor::MakeDefinitionListItemWithTransaction(). But this
3447 // difference may be a bug. We should investigate this later.
3448 IgnoredErrorResult error;
3449 AutoEditSubActionNotifier startToHandleEditSubAction(
3450 *this,
3451 &aListElementOrListItemElementTagName == nsGkAtoms::dd ||
3452 &aListElementOrListItemElementTagName == nsGkAtoms::dt
3453 ? EditSubAction::eCreateOrChangeDefinitionListItem
3454 : EditSubAction::eCreateOrChangeList,
3455 nsIEditor::eNext, error);
3456 if (NS_WARN_IF(error.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
3457 return Err(error.StealNSResult());
3459 NS_WARNING_ASSERTION(
3460 !error.Failed(),
3461 "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
3463 nsresult rv = EnsureNoPaddingBRElementForEmptyEditor();
3464 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
3465 return Err(NS_ERROR_EDITOR_DESTROYED);
3467 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
3468 "EditorBase::EnsureNoPaddingBRElementForEmptyEditor() "
3469 "failed, but ignored");
3471 if (NS_SUCCEEDED(rv) && SelectionRef().IsCollapsed()) {
3472 nsresult rv = EnsureCaretNotAfterInvisibleBRElement();
3473 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
3474 return Err(NS_ERROR_EDITOR_DESTROYED);
3476 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
3477 "HTMLEditor::EnsureCaretNotAfterInvisibleBRElement() "
3478 "failed, but ignored");
3479 if (NS_SUCCEEDED(rv)) {
3480 nsresult rv = PrepareInlineStylesForCaret();
3481 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
3482 return Err(NS_ERROR_EDITOR_DESTROYED);
3484 NS_WARNING_ASSERTION(
3485 NS_SUCCEEDED(rv),
3486 "HTMLEditor::PrepareInlineStylesForCaret() failed, but ignored");
3490 const nsStaticAtom* listTagName = nullptr;
3491 const nsStaticAtom* listItemTagName = nullptr;
3492 if (&aListElementOrListItemElementTagName == nsGkAtoms::ul ||
3493 &aListElementOrListItemElementTagName == nsGkAtoms::ol) {
3494 listTagName = &aListElementOrListItemElementTagName;
3495 listItemTagName = nsGkAtoms::li;
3496 } else if (&aListElementOrListItemElementTagName == nsGkAtoms::dl) {
3497 listTagName = &aListElementOrListItemElementTagName;
3498 listItemTagName = nsGkAtoms::dd;
3499 } else if (&aListElementOrListItemElementTagName == nsGkAtoms::dd ||
3500 &aListElementOrListItemElementTagName == nsGkAtoms::dt) {
3501 listTagName = nsGkAtoms::dl;
3502 listItemTagName = &aListElementOrListItemElementTagName;
3503 } else {
3504 NS_WARNING(
3505 "aListElementOrListItemElementTagName was neither list element name "
3506 "nor "
3507 "definition listitem element name");
3508 return Err(NS_ERROR_INVALID_ARG);
3511 const RefPtr<Element> editingHost = ComputeEditingHost();
3512 if (MOZ_UNLIKELY(!editingHost)) {
3513 return EditActionResult::CanceledResult();
3516 // Expands selection range to include the immediate block parent, and then
3517 // further expands to include any ancestors whose children are all in the
3518 // range.
3519 // XXX Why do we do this only when there is only one selection range?
3520 if (!SelectionRef().IsCollapsed() && SelectionRef().RangeCount() == 1u) {
3521 Result<EditorRawDOMRange, nsresult> extendedRange =
3522 GetRangeExtendedToHardLineEdgesForBlockEditAction(
3523 SelectionRef().GetRangeAt(0u), *editingHost);
3524 if (MOZ_UNLIKELY(extendedRange.isErr())) {
3525 NS_WARNING(
3526 "HTMLEditor::GetRangeExtendedToHardLineEdgesForBlockEditAction() "
3527 "failed");
3528 return extendedRange.propagateErr();
3530 // Note that end point may be prior to start point. So, we
3531 // cannot use Selection::SetStartAndEndInLimit() here.
3532 error.SuppressException();
3533 SelectionRef().SetBaseAndExtentInLimiter(
3534 extendedRange.inspect().StartRef().ToRawRangeBoundary(),
3535 extendedRange.inspect().EndRef().ToRawRangeBoundary(), error);
3536 if (NS_WARN_IF(Destroyed())) {
3537 return Err(NS_ERROR_EDITOR_DESTROYED);
3539 if (MOZ_UNLIKELY(error.Failed())) {
3540 NS_WARNING("Selection::SetBaseAndExtentInLimiter() failed");
3541 return Err(error.StealNSResult());
3545 AutoListElementCreator listCreator(*listTagName, *listItemTagName,
3546 aBulletType);
3547 AutoRangeArray selectionRanges(SelectionRef());
3548 Result<EditActionResult, nsresult> result = listCreator.Run(
3549 *this, selectionRanges, aSelectAllOfCurrentList, *editingHost);
3550 if (MOZ_UNLIKELY(result.isErr())) {
3551 NS_WARNING("HTMLEditor::ConvertContentAroundRangesToList() failed");
3552 // XXX Should we try to restore selection ranges in this case?
3553 return result;
3556 rv = selectionRanges.ApplyTo(SelectionRef());
3557 if (NS_WARN_IF(Destroyed())) {
3558 return Err(NS_ERROR_EDITOR_DESTROYED);
3560 if (NS_FAILED(rv)) {
3561 NS_WARNING("AutoRangeArray::ApplyTo() failed");
3562 return Err(rv);
3564 return result.inspect().Ignored() ? EditActionResult::CanceledResult()
3565 : EditActionResult::HandledResult();
3568 Result<EditActionResult, nsresult> HTMLEditor::AutoListElementCreator::Run(
3569 HTMLEditor& aHTMLEditor, AutoRangeArray& aRanges,
3570 SelectAllOfCurrentList aSelectAllOfCurrentList,
3571 const Element& aEditingHost) const {
3572 MOZ_ASSERT(aHTMLEditor.IsTopLevelEditSubActionDataAvailable());
3573 MOZ_ASSERT(!aHTMLEditor.IsSelectionRangeContainerNotContent());
3575 if (NS_WARN_IF(!aRanges.SaveAndTrackRanges(aHTMLEditor))) {
3576 return Err(NS_ERROR_FAILURE);
3579 AutoContentNodeArray arrayOfContents;
3580 nsresult rv = SplitAtRangeEdgesAndCollectContentNodesToMoveIntoList(
3581 aHTMLEditor, aRanges, aSelectAllOfCurrentList, aEditingHost,
3582 arrayOfContents);
3583 if (NS_FAILED(rv)) {
3584 NS_WARNING(
3585 "AutoListElementCreator::"
3586 "SplitAtRangeEdgesAndCollectContentNodesToMoveIntoList() failed");
3587 return Err(rv);
3590 // check if all our nodes are <br>s, or empty inlines
3591 // if no nodes, we make empty list. Ditto if the user tried to make a list
3592 // of some # of breaks.
3593 if (AutoListElementCreator::
3594 IsEmptyOrContainsOnlyBRElementsOrEmptyInlineElements(
3595 arrayOfContents)) {
3596 Result<RefPtr<Element>, nsresult> newListItemElementOrError =
3597 ReplaceContentNodesWithEmptyNewList(aHTMLEditor, aRanges,
3598 arrayOfContents, aEditingHost);
3599 if (MOZ_UNLIKELY(newListItemElementOrError.isErr())) {
3600 NS_WARNING(
3601 "AutoListElementCreator::ReplaceContentNodesWithEmptyNewList() "
3602 "failed");
3603 return newListItemElementOrError.propagateErr();
3605 if (MOZ_UNLIKELY(!newListItemElementOrError.inspect())) {
3606 aRanges.RestoreFromSavedRanges();
3607 return EditActionResult::CanceledResult();
3609 aRanges.ClearSavedRanges();
3610 nsresult rv = aRanges.Collapse(
3611 EditorRawDOMPoint(newListItemElementOrError.inspect(), 0u));
3612 if (NS_FAILED(rv)) {
3613 NS_WARNING("AutoRangeArray::Collapse() failed");
3614 return Err(rv);
3616 return EditActionResult::IgnoredResult();
3619 Result<RefPtr<Element>, nsresult> listItemOrListToPutCaretOrError =
3620 WrapContentNodesIntoNewListElements(aHTMLEditor, aRanges, arrayOfContents,
3621 aEditingHost);
3622 if (MOZ_UNLIKELY(listItemOrListToPutCaretOrError.isErr())) {
3623 NS_WARNING(
3624 "AutoListElementCreator::WrapContentNodesIntoNewListElements() failed");
3625 return listItemOrListToPutCaretOrError.propagateErr();
3628 MOZ_ASSERT(aRanges.HasSavedRanges());
3629 aRanges.RestoreFromSavedRanges();
3631 // If selection will be collapsed but not in listItemOrListToPutCaret, we need
3632 // to adjust the caret position into it.
3633 if (listItemOrListToPutCaretOrError.inspect()) {
3634 DebugOnly<nsresult> rvIgnored =
3635 EnsureCollapsedRangeIsInListItemOrListElement(
3636 *listItemOrListToPutCaretOrError.inspect(), aRanges);
3637 NS_WARNING_ASSERTION(
3638 NS_SUCCEEDED(rvIgnored),
3639 "AutoListElementCreator::"
3640 "EnsureCollapsedRangeIsInListItemOrListElement() failed, but ignored");
3643 return EditActionResult::HandledResult();
3646 nsresult HTMLEditor::AutoListElementCreator::
3647 SplitAtRangeEdgesAndCollectContentNodesToMoveIntoList(
3648 HTMLEditor& aHTMLEditor, AutoRangeArray& aRanges,
3649 SelectAllOfCurrentList aSelectAllOfCurrentList,
3650 const Element& aEditingHost,
3651 ContentNodeArray& aOutArrayOfContents) const {
3652 MOZ_ASSERT(aOutArrayOfContents.IsEmpty());
3654 if (aSelectAllOfCurrentList == SelectAllOfCurrentList::Yes) {
3655 if (Element* parentListElementOfRanges =
3656 aRanges.GetClosestAncestorAnyListElementOfRange()) {
3657 aOutArrayOfContents.AppendElement(
3658 OwningNonNull<nsIContent>(*parentListElementOfRanges));
3659 return NS_OK;
3663 AutoRangeArray extendedRanges(aRanges);
3665 // TODO: We don't need AutoTransactionsConserveSelection here in the
3666 // normal cases, but removing this may cause the behavior with the
3667 // legacy mutation event listeners. We should try to delete this in
3668 // a bug.
3669 AutoTransactionsConserveSelection dontChangeMySelection(aHTMLEditor);
3671 extendedRanges.ExtendRangesToWrapLines(EditSubAction::eCreateOrChangeList,
3672 BlockInlineCheck::UseHTMLDefaultStyle,
3673 aEditingHost);
3674 Result<EditorDOMPoint, nsresult> splitResult =
3675 extendedRanges.SplitTextAtEndBoundariesAndInlineAncestorsAtBothBoundaries(
3676 aHTMLEditor, BlockInlineCheck::UseHTMLDefaultStyle, aEditingHost);
3677 if (MOZ_UNLIKELY(splitResult.isErr())) {
3678 NS_WARNING(
3679 "AutoRangeArray::"
3680 "SplitTextAtEndBoundariesAndInlineAncestorsAtBothBoundaries() failed");
3681 return splitResult.unwrapErr();
3683 nsresult rv = extendedRanges.CollectEditTargetNodes(
3684 aHTMLEditor, aOutArrayOfContents, EditSubAction::eCreateOrChangeList,
3685 AutoRangeArray::CollectNonEditableNodes::No);
3686 if (NS_FAILED(rv)) {
3687 NS_WARNING(
3688 "AutoRangeArray::CollectEditTargetNodes(EditSubAction::"
3689 "eCreateOrChangeList, CollectNonEditableNodes::No) failed");
3690 return rv;
3693 Result<EditorDOMPoint, nsresult> splitAtBRElementsResult =
3694 aHTMLEditor.MaybeSplitElementsAtEveryBRElement(
3695 aOutArrayOfContents, EditSubAction::eCreateOrChangeList);
3696 if (MOZ_UNLIKELY(splitAtBRElementsResult.isErr())) {
3697 NS_WARNING(
3698 "HTMLEditor::MaybeSplitElementsAtEveryBRElement(EditSubAction::"
3699 "eCreateOrChangeList) failed");
3700 return splitAtBRElementsResult.unwrapErr();
3702 return NS_OK;
3705 // static
3706 bool HTMLEditor::AutoListElementCreator::
3707 IsEmptyOrContainsOnlyBRElementsOrEmptyInlineElements(
3708 const ContentNodeArray& aArrayOfContents) {
3709 for (const OwningNonNull<nsIContent>& content : aArrayOfContents) {
3710 // if content is not a <br> or empty inline, we're done
3711 // XXX Should we handle line breaks in preformatted text node?
3712 if (!content->IsHTMLElement(nsGkAtoms::br) &&
3713 !HTMLEditUtils::IsEmptyInlineContainer(
3714 content,
3715 {EmptyCheckOption::TreatSingleBRElementAsVisible,
3716 EmptyCheckOption::TreatNonEditableContentAsInvisible},
3717 BlockInlineCheck::UseComputedDisplayStyle)) {
3718 return false;
3721 return true;
3724 Result<RefPtr<Element>, nsresult>
3725 HTMLEditor::AutoListElementCreator::ReplaceContentNodesWithEmptyNewList(
3726 HTMLEditor& aHTMLEditor, const AutoRangeArray& aRanges,
3727 const AutoContentNodeArray& aArrayOfContents,
3728 const Element& aEditingHost) const {
3729 // if only breaks, delete them
3730 for (const OwningNonNull<nsIContent>& content : aArrayOfContents) {
3731 // MOZ_KnownLive because of bug 1620312
3732 nsresult rv =
3733 aHTMLEditor.DeleteNodeWithTransaction(MOZ_KnownLive(*content));
3734 if (NS_FAILED(rv)) {
3735 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
3736 return Err(rv);
3740 const auto firstRangeStartPoint =
3741 aRanges.GetFirstRangeStartPoint<EditorDOMPoint>();
3742 if (NS_WARN_IF(!firstRangeStartPoint.IsSet())) {
3743 return Err(NS_ERROR_FAILURE);
3746 // Make sure we can put a list here.
3747 if (!HTMLEditUtils::CanNodeContain(*firstRangeStartPoint.GetContainer(),
3748 mListTagName)) {
3749 return RefPtr<Element>();
3752 RefPtr<Element> newListItemElement;
3753 Result<CreateElementResult, nsresult> createNewListElementResult =
3754 aHTMLEditor.InsertElementWithSplittingAncestorsWithTransaction(
3755 mListTagName, firstRangeStartPoint, BRElementNextToSplitPoint::Keep,
3756 aEditingHost,
3757 // MOZ_CAN_RUN_SCRIPT_BOUNDARY due to bug 1758868
3758 [&](HTMLEditor& aHTMLEditor, Element& aListElement,
3759 const EditorDOMPoint&) MOZ_CAN_RUN_SCRIPT_BOUNDARY {
3760 AutoHandlingState dummyState;
3761 Result<CreateElementResult, nsresult> createListItemElementResult =
3762 AppendListItemElement(aHTMLEditor, aListElement, dummyState);
3763 if (MOZ_UNLIKELY(createListItemElementResult.isErr())) {
3764 NS_WARNING(
3765 "AutoListElementCreator::AppendListItemElement() failed");
3766 return createListItemElementResult.unwrapErr();
3768 CreateElementResult unwrappedResult =
3769 createListItemElementResult.unwrap();
3770 // There is AutoSelectionRestorer in this method so that it'll
3771 // be restored or updated with making it abort. Therefore,
3772 // we don't need to update selection here.
3773 // XXX I'd like to check aRanges.HasSavedRanges() here, but it
3774 // requires ifdefs to avoid bustage of opt builds caused
3775 // by unused warning...
3776 unwrappedResult.IgnoreCaretPointSuggestion();
3777 newListItemElement = unwrappedResult.UnwrapNewNode();
3778 MOZ_ASSERT(newListItemElement);
3779 return NS_OK;
3781 if (MOZ_UNLIKELY(createNewListElementResult.isErr())) {
3782 NS_WARNING(
3783 nsPrintfCString(
3784 "HTMLEditor::InsertElementWithSplittingAncestorsWithTransaction("
3785 "%s) failed",
3786 nsAtomCString(&mListTagName).get())
3787 .get());
3788 return createNewListElementResult.propagateErr();
3790 MOZ_ASSERT(createNewListElementResult.inspect().GetNewNode());
3792 // Put selection in new list item and don't restore the Selection.
3793 createNewListElementResult.inspect().IgnoreCaretPointSuggestion();
3794 return newListItemElement;
3797 Result<RefPtr<Element>, nsresult>
3798 HTMLEditor::AutoListElementCreator::WrapContentNodesIntoNewListElements(
3799 HTMLEditor& aHTMLEditor, AutoRangeArray& aRanges,
3800 AutoContentNodeArray& aArrayOfContents, const Element& aEditingHost) const {
3801 // if there is only one node in the array, and it is a list, div, or
3802 // blockquote, then look inside of it until we find inner list or content.
3803 if (aArrayOfContents.Length() == 1) {
3804 if (Element* deepestDivBlockquoteOrListElement =
3805 HTMLEditUtils::GetInclusiveDeepestFirstChildWhichHasOneChild(
3806 aArrayOfContents[0], {WalkTreeOption::IgnoreNonEditableNode},
3807 BlockInlineCheck::UseHTMLDefaultStyle, nsGkAtoms::div,
3808 nsGkAtoms::blockquote, nsGkAtoms::ul, nsGkAtoms::ol,
3809 nsGkAtoms::dl)) {
3810 if (deepestDivBlockquoteOrListElement->IsAnyOfHTMLElements(
3811 nsGkAtoms::div, nsGkAtoms::blockquote)) {
3812 aArrayOfContents.Clear();
3813 HTMLEditUtils::CollectChildren(*deepestDivBlockquoteOrListElement,
3814 aArrayOfContents, 0, {});
3815 } else {
3816 aArrayOfContents.ReplaceElementAt(
3817 0, OwningNonNull<nsIContent>(*deepestDivBlockquoteOrListElement));
3822 // Ok, now go through all the nodes and put then in the list,
3823 // or whatever is appropriate. Wohoo!
3824 AutoHandlingState handlingState;
3825 for (const OwningNonNull<nsIContent>& content : aArrayOfContents) {
3826 // MOZ_KnownLive because of bug 1620312
3827 nsresult rv = HandleChildContent(aHTMLEditor, MOZ_KnownLive(content),
3828 handlingState, aEditingHost);
3829 if (NS_FAILED(rv)) {
3830 NS_WARNING("AutoListElementCreator::HandleChildContent() failed");
3831 return Err(rv);
3835 return std::move(handlingState.mListOrListItemElementToPutCaret);
3838 nsresult HTMLEditor::AutoListElementCreator::HandleChildContent(
3839 HTMLEditor& aHTMLEditor, nsIContent& aHandlingContent,
3840 AutoHandlingState& aState, const Element& aEditingHost) const {
3841 // make sure we don't assemble content that is in different table cells
3842 // into the same list. respect table cell boundaries when listifying.
3843 if (aState.mCurrentListElement &&
3844 HTMLEditUtils::GetInclusiveAncestorAnyTableElement(
3845 *aState.mCurrentListElement) !=
3846 HTMLEditUtils::GetInclusiveAncestorAnyTableElement(
3847 aHandlingContent)) {
3848 aState.mCurrentListElement = nullptr;
3851 // If current node is a `<br>` element, delete it and forget previous
3852 // list item element.
3853 // If current node is an empty inline node, just delete it.
3854 if (EditorUtils::IsEditableContent(aHandlingContent, EditorType::HTML) &&
3855 (aHandlingContent.IsHTMLElement(nsGkAtoms::br) ||
3856 HTMLEditUtils::IsEmptyInlineContainer(
3857 aHandlingContent,
3858 {EmptyCheckOption::TreatSingleBRElementAsVisible,
3859 EmptyCheckOption::TreatNonEditableContentAsInvisible},
3860 BlockInlineCheck::UseHTMLDefaultStyle))) {
3861 nsresult rv = aHTMLEditor.DeleteNodeWithTransaction(aHandlingContent);
3862 if (NS_FAILED(rv)) {
3863 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
3864 return rv;
3866 if (aHandlingContent.IsHTMLElement(nsGkAtoms::br)) {
3867 aState.mPreviousListItemElement = nullptr;
3869 return NS_OK;
3872 // If we meet a list, we can reuse it or convert it to the expected type list.
3873 if (HTMLEditUtils::IsAnyListElement(&aHandlingContent)) {
3874 nsresult rv = HandleChildListElement(
3875 aHTMLEditor, MOZ_KnownLive(*aHandlingContent.AsElement()), aState);
3876 NS_WARNING_ASSERTION(
3877 NS_SUCCEEDED(rv),
3878 "AutoListElementCreator::HandleChildListElement() failed");
3879 return rv;
3882 // We cannot handle nodes if not in element node.
3883 if (NS_WARN_IF(!aHandlingContent.GetParentElement())) {
3884 return NS_ERROR_FAILURE;
3887 // If we meet a list item, we can just move it to current list element or new
3888 // list element.
3889 if (HTMLEditUtils::IsListItem(&aHandlingContent)) {
3890 nsresult rv = HandleChildListItemElement(
3891 aHTMLEditor, MOZ_KnownLive(*aHandlingContent.AsElement()), aState);
3892 NS_WARNING_ASSERTION(
3893 NS_SUCCEEDED(rv),
3894 "AutoListElementCreator::HandleChildListItemElement() failed");
3895 return rv;
3898 // If we meet a <div> or a <p>, we want only its children to wrapping into
3899 // list element. Therefore, this call will call this recursively.
3900 if (aHandlingContent.IsAnyOfHTMLElements(nsGkAtoms::div, nsGkAtoms::p)) {
3901 nsresult rv = HandleChildDivOrParagraphElement(
3902 aHTMLEditor, MOZ_KnownLive(*aHandlingContent.AsElement()), aState,
3903 aEditingHost);
3904 NS_WARNING_ASSERTION(
3905 NS_SUCCEEDED(rv),
3906 "AutoListElementCreator::HandleChildDivOrParagraphElement() failed");
3907 return rv;
3910 // If we've not met a list element, create a list element and make it
3911 // current list element.
3912 if (!aState.mCurrentListElement) {
3913 nsresult rv = CreateAndUpdateCurrentListElement(
3914 aHTMLEditor, EditorDOMPoint(&aHandlingContent),
3915 EmptyListItem::NotCreate, aState, aEditingHost);
3916 if (NS_FAILED(rv)) {
3917 NS_WARNING("AutoListElementCreator::HandleChildInlineElement() failed");
3918 return rv;
3922 // If we meet an inline content, we want to move it to previously used list
3923 // item element or new list item element.
3924 if (HTMLEditUtils::IsInlineContent(aHandlingContent,
3925 BlockInlineCheck::UseHTMLDefaultStyle)) {
3926 nsresult rv =
3927 HandleChildInlineContent(aHTMLEditor, aHandlingContent, aState);
3928 NS_WARNING_ASSERTION(
3929 NS_SUCCEEDED(rv),
3930 "AutoListElementCreator::HandleChildInlineElement() failed");
3931 return rv;
3934 // Otherwise, we should wrap it into new list item element.
3935 nsresult rv =
3936 WrapContentIntoNewListItemElement(aHTMLEditor, aHandlingContent, aState);
3937 NS_WARNING_ASSERTION(
3938 NS_SUCCEEDED(rv),
3939 "AutoListElementCreator::WrapContentIntoNewListItemElement() failed");
3940 return rv;
3943 nsresult HTMLEditor::AutoListElementCreator::HandleChildListElement(
3944 HTMLEditor& aHTMLEditor, Element& aHandlingListElement,
3945 AutoHandlingState& aState) const {
3946 MOZ_ASSERT(HTMLEditUtils::IsAnyListElement(&aHandlingListElement));
3948 // If we met a list element and current list element is not a descendant
3949 // of the list, append current node to end of the current list element.
3950 // Then, wrap it with list item element and delete the old container.
3951 if (aState.mCurrentListElement &&
3952 !EditorUtils::IsDescendantOf(aHandlingListElement,
3953 *aState.mCurrentListElement)) {
3954 Result<MoveNodeResult, nsresult> moveNodeResult =
3955 aHTMLEditor.MoveNodeToEndWithTransaction(
3956 aHandlingListElement, MOZ_KnownLive(*aState.mCurrentListElement));
3957 if (MOZ_UNLIKELY(moveNodeResult.isErr())) {
3958 NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed");
3959 return moveNodeResult.propagateErr();
3961 moveNodeResult.inspect().IgnoreCaretPointSuggestion();
3963 Result<CreateElementResult, nsresult> convertListTypeResult =
3964 aHTMLEditor.ChangeListElementType(aHandlingListElement, mListTagName,
3965 mListItemTagName);
3966 if (MOZ_UNLIKELY(convertListTypeResult.isErr())) {
3967 NS_WARNING("HTMLEditor::ChangeListElementType() failed");
3968 return convertListTypeResult.propagateErr();
3970 convertListTypeResult.inspect().IgnoreCaretPointSuggestion();
3972 Result<EditorDOMPoint, nsresult> unwrapNewListElementResult =
3973 aHTMLEditor.RemoveBlockContainerWithTransaction(
3974 MOZ_KnownLive(*convertListTypeResult.inspect().GetNewNode()));
3975 if (MOZ_UNLIKELY(unwrapNewListElementResult.isErr())) {
3976 NS_WARNING("HTMLEditor::RemoveBlockContainerWithTransaction() failed");
3977 return unwrapNewListElementResult.propagateErr();
3979 aState.mPreviousListItemElement = nullptr;
3980 return NS_OK;
3983 // If current list element is in found list element or we've not met a
3984 // list element, convert current list element to proper type.
3985 Result<CreateElementResult, nsresult> convertListTypeResult =
3986 aHTMLEditor.ChangeListElementType(aHandlingListElement, mListTagName,
3987 mListItemTagName);
3988 if (MOZ_UNLIKELY(convertListTypeResult.isErr())) {
3989 NS_WARNING("HTMLEditor::ChangeListElementType() failed");
3990 return convertListTypeResult.propagateErr();
3992 CreateElementResult unwrappedConvertListTypeResult =
3993 convertListTypeResult.unwrap();
3994 unwrappedConvertListTypeResult.IgnoreCaretPointSuggestion();
3995 MOZ_ASSERT(unwrappedConvertListTypeResult.GetNewNode());
3996 aState.mCurrentListElement = unwrappedConvertListTypeResult.UnwrapNewNode();
3997 aState.mPreviousListItemElement = nullptr;
3998 return NS_OK;
4001 nsresult
4002 HTMLEditor::AutoListElementCreator::HandleChildListItemInDifferentTypeList(
4003 HTMLEditor& aHTMLEditor, Element& aHandlingListItemElement,
4004 AutoHandlingState& aState) const {
4005 MOZ_ASSERT(HTMLEditUtils::IsListItem(&aHandlingListItemElement));
4006 MOZ_ASSERT(
4007 !aHandlingListItemElement.GetParent()->IsHTMLElement(&mListTagName));
4009 // If we've not met a list element or current node is not in current list
4010 // element, insert a list element at current node and set current list element
4011 // to the new one.
4012 if (!aState.mCurrentListElement ||
4013 aHandlingListItemElement.IsInclusiveDescendantOf(
4014 aState.mCurrentListElement)) {
4015 EditorDOMPoint atListItem(&aHandlingListItemElement);
4016 MOZ_ASSERT(atListItem.IsInContentNode());
4018 Result<SplitNodeResult, nsresult> splitListItemParentResult =
4019 aHTMLEditor.SplitNodeWithTransaction(atListItem);
4020 if (MOZ_UNLIKELY(splitListItemParentResult.isErr())) {
4021 NS_WARNING("HTMLEditor::SplitNodeWithTransaction() failed");
4022 return splitListItemParentResult.propagateErr();
4024 SplitNodeResult unwrappedSplitListItemParentResult =
4025 splitListItemParentResult.unwrap();
4026 MOZ_ASSERT(unwrappedSplitListItemParentResult.DidSplit());
4027 unwrappedSplitListItemParentResult.IgnoreCaretPointSuggestion();
4029 Result<CreateElementResult, nsresult> createNewListElementResult =
4030 aHTMLEditor.CreateAndInsertElement(
4031 WithTransaction::Yes, mListTagName,
4032 unwrappedSplitListItemParentResult.AtNextContent<EditorDOMPoint>());
4033 if (MOZ_UNLIKELY(createNewListElementResult.isErr())) {
4034 NS_WARNING(
4035 "HTMLEditor::CreateAndInsertElement(WithTransaction::Yes) "
4036 "failed");
4037 return createNewListElementResult.propagateErr();
4039 CreateElementResult unwrapCreateNewListElementResult =
4040 createNewListElementResult.unwrap();
4041 unwrapCreateNewListElementResult.IgnoreCaretPointSuggestion();
4042 MOZ_ASSERT(unwrapCreateNewListElementResult.GetNewNode());
4043 aState.mCurrentListElement =
4044 unwrapCreateNewListElementResult.UnwrapNewNode();
4047 // Then, move current node into current list element.
4048 Result<MoveNodeResult, nsresult> moveNodeResult =
4049 aHTMLEditor.MoveNodeToEndWithTransaction(
4050 aHandlingListItemElement, MOZ_KnownLive(*aState.mCurrentListElement));
4051 if (MOZ_UNLIKELY(moveNodeResult.isErr())) {
4052 NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed");
4053 return moveNodeResult.propagateErr();
4055 moveNodeResult.inspect().IgnoreCaretPointSuggestion();
4057 // Convert list item type if current node is different list item type.
4058 if (aHandlingListItemElement.IsHTMLElement(&mListItemTagName)) {
4059 return NS_OK;
4061 Result<CreateElementResult, nsresult> newListItemElementOrError =
4062 aHTMLEditor.ReplaceContainerAndCloneAttributesWithTransaction(
4063 aHandlingListItemElement, mListItemTagName);
4064 if (MOZ_UNLIKELY(newListItemElementOrError.isErr())) {
4065 NS_WARNING("HTMLEditor::ReplaceContainerWithTransaction() failed");
4066 return newListItemElementOrError.propagateErr();
4068 newListItemElementOrError.inspect().IgnoreCaretPointSuggestion();
4069 return NS_OK;
4072 nsresult HTMLEditor::AutoListElementCreator::HandleChildListItemElement(
4073 HTMLEditor& aHTMLEditor, Element& aHandlingListItemElement,
4074 AutoHandlingState& aState) const {
4075 MOZ_ASSERT(aHandlingListItemElement.GetParentNode());
4076 MOZ_ASSERT(HTMLEditUtils::IsListItem(&aHandlingListItemElement));
4078 // If current list item element is not in proper list element, we need
4079 // to convert the list element.
4080 // XXX This check is not enough,
4081 if (!aHandlingListItemElement.GetParentNode()->IsHTMLElement(&mListTagName)) {
4082 nsresult rv = HandleChildListItemInDifferentTypeList(
4083 aHTMLEditor, aHandlingListItemElement, aState);
4084 if (NS_FAILED(rv)) {
4085 NS_WARNING(
4086 "AutoListElementCreator::HandleChildListItemInDifferentTypeList() "
4087 "failed");
4088 return rv;
4090 } else {
4091 nsresult rv = HandleChildListItemInSameTypeList(
4092 aHTMLEditor, aHandlingListItemElement, aState);
4093 if (NS_FAILED(rv)) {
4094 NS_WARNING(
4095 "AutoListElementCreator::HandleChildListItemInSameTypeList() failed");
4096 return rv;
4100 // If bullet type is specified, set list type attribute.
4101 // XXX Cannot we set type attribute before inserting the list item
4102 // element into the DOM tree?
4103 if (!mBulletType.IsEmpty()) {
4104 nsresult rv = aHTMLEditor.SetAttributeWithTransaction(
4105 aHandlingListItemElement, *nsGkAtoms::type, mBulletType);
4106 if (NS_WARN_IF(aHTMLEditor.Destroyed())) {
4107 return NS_ERROR_EDITOR_DESTROYED;
4109 NS_WARNING_ASSERTION(
4110 NS_SUCCEEDED(rv),
4111 "EditorBase::SetAttributeWithTransaction(nsGkAtoms::type) failed");
4112 return rv;
4115 // Otherwise, remove list type attribute if there is.
4116 if (!aHandlingListItemElement.HasAttr(nsGkAtoms::type)) {
4117 return NS_OK;
4119 nsresult rv = aHTMLEditor.RemoveAttributeWithTransaction(
4120 aHandlingListItemElement, *nsGkAtoms::type);
4121 NS_WARNING_ASSERTION(
4122 NS_SUCCEEDED(rv),
4123 "EditorBase::RemoveAttributeWithTransaction(nsGkAtoms::type) failed");
4124 return rv;
4127 nsresult HTMLEditor::AutoListElementCreator::HandleChildListItemInSameTypeList(
4128 HTMLEditor& aHTMLEditor, Element& aHandlingListItemElement,
4129 AutoHandlingState& aState) const {
4130 MOZ_ASSERT(HTMLEditUtils::IsListItem(&aHandlingListItemElement));
4131 MOZ_ASSERT(
4132 aHandlingListItemElement.GetParent()->IsHTMLElement(&mListTagName));
4134 EditorDOMPoint atListItem(&aHandlingListItemElement);
4135 MOZ_ASSERT(atListItem.IsInContentNode());
4137 // If we've not met a list element, set current list element to the
4138 // parent of current list item element.
4139 if (!aState.mCurrentListElement) {
4140 aState.mCurrentListElement = atListItem.GetContainerAs<Element>();
4141 NS_WARNING_ASSERTION(
4142 HTMLEditUtils::IsAnyListElement(aState.mCurrentListElement),
4143 "Current list item parent is not a list element");
4145 // If current list item element is not a child of current list element,
4146 // move it into current list item.
4147 else if (atListItem.GetContainer() != aState.mCurrentListElement) {
4148 Result<MoveNodeResult, nsresult> moveNodeResult =
4149 aHTMLEditor.MoveNodeToEndWithTransaction(
4150 aHandlingListItemElement,
4151 MOZ_KnownLive(*aState.mCurrentListElement));
4152 if (MOZ_UNLIKELY(moveNodeResult.isErr())) {
4153 NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed");
4154 return moveNodeResult.propagateErr();
4156 moveNodeResult.inspect().IgnoreCaretPointSuggestion();
4159 // Then, if current list item element is not proper type for current
4160 // list element, convert list item element to proper element.
4161 if (aHandlingListItemElement.IsHTMLElement(&mListItemTagName)) {
4162 return NS_OK;
4164 // FIXME: Manage attribute cloning
4165 Result<CreateElementResult, nsresult> newListItemElementOrError =
4166 aHTMLEditor.ReplaceContainerAndCloneAttributesWithTransaction(
4167 aHandlingListItemElement, mListItemTagName);
4168 if (MOZ_UNLIKELY(newListItemElementOrError.isErr())) {
4169 NS_WARNING(
4170 "HTMLEditor::ReplaceContainerAndCloneAttributesWithTransaction() "
4171 "failed");
4172 return newListItemElementOrError.propagateErr();
4174 newListItemElementOrError.inspect().IgnoreCaretPointSuggestion();
4175 return NS_OK;
4178 nsresult HTMLEditor::AutoListElementCreator::HandleChildDivOrParagraphElement(
4179 HTMLEditor& aHTMLEditor, Element& aHandlingDivOrParagraphElement,
4180 AutoHandlingState& aState, const Element& aEditingHost) const {
4181 MOZ_ASSERT(aHandlingDivOrParagraphElement.IsAnyOfHTMLElements(nsGkAtoms::div,
4182 nsGkAtoms::p));
4184 AutoRestore<RefPtr<Element>> previouslyReplacingBlockElement(
4185 aState.mReplacingBlockElement);
4186 aState.mReplacingBlockElement = &aHandlingDivOrParagraphElement;
4187 AutoRestore<bool> previouslyReplacingBlockElementIdCopied(
4188 aState.mMaybeCopiedReplacingBlockElementId);
4189 aState.mMaybeCopiedReplacingBlockElementId = false;
4191 // If the <div> or <p> is empty, we should replace it with a list element
4192 // and/or a list item element.
4193 if (HTMLEditUtils::IsEmptyNode(aHandlingDivOrParagraphElement,
4194 {EmptyCheckOption::TreatListItemAsVisible,
4195 EmptyCheckOption::TreatTableCellAsVisible})) {
4196 if (!aState.mCurrentListElement) {
4197 nsresult rv = CreateAndUpdateCurrentListElement(
4198 aHTMLEditor, EditorDOMPoint(&aHandlingDivOrParagraphElement),
4199 EmptyListItem::Create, aState, aEditingHost);
4200 if (NS_FAILED(rv)) {
4201 NS_WARNING(
4202 "AutoListElementCreator::CreateAndUpdateCurrentListElement("
4203 "EmptyListItem::Create) failed");
4204 return rv;
4206 } else {
4207 Result<CreateElementResult, nsresult> createListItemElementResult =
4208 AppendListItemElement(
4209 aHTMLEditor, MOZ_KnownLive(*aState.mCurrentListElement), aState);
4210 if (MOZ_UNLIKELY(createListItemElementResult.isErr())) {
4211 NS_WARNING("AutoListElementCreator::AppendListItemElement() failed");
4212 return createListItemElementResult.unwrapErr();
4214 CreateElementResult unwrappedResult =
4215 createListItemElementResult.unwrap();
4216 unwrappedResult.IgnoreCaretPointSuggestion();
4217 aState.mListOrListItemElementToPutCaret = unwrappedResult.UnwrapNewNode();
4219 nsresult rv =
4220 aHTMLEditor.DeleteNodeWithTransaction(aHandlingDivOrParagraphElement);
4221 if (NS_FAILED(rv)) {
4222 NS_WARNING("HTMLEditor::DeleteNodeWithTransaction() failed");
4223 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 // If current node is a <div> element, replace it with its children and handle
4235 // them as same as topmost children in the range.
4236 AutoContentNodeArray arrayOfContentsInDiv;
4237 HTMLEditUtils::CollectChildren(aHandlingDivOrParagraphElement,
4238 arrayOfContentsInDiv, 0,
4239 {CollectChildrenOption::CollectListChildren,
4240 CollectChildrenOption::CollectTableChildren});
4242 Result<EditorDOMPoint, nsresult> unwrapDivElementResult =
4243 aHTMLEditor.RemoveContainerWithTransaction(
4244 aHandlingDivOrParagraphElement);
4245 if (MOZ_UNLIKELY(unwrapDivElementResult.isErr())) {
4246 NS_WARNING("HTMLEditor::RemoveContainerWithTransaction() failed");
4247 return unwrapDivElementResult.unwrapErr();
4250 for (const OwningNonNull<nsIContent>& content : arrayOfContentsInDiv) {
4251 // MOZ_KnownLive because of bug 1620312
4252 nsresult rv = HandleChildContent(aHTMLEditor, MOZ_KnownLive(content),
4253 aState, aEditingHost);
4254 if (NS_FAILED(rv)) {
4255 NS_WARNING("AutoListElementCreator::HandleChildContent() failed");
4256 return rv;
4260 // We don't want new inline contents inserted into the new list item element
4261 // because we want to keep the line break at end of
4262 // aHandlingDivOrParagraphElement.
4263 aState.mPreviousListItemElement = nullptr;
4265 return NS_OK;
4268 nsresult HTMLEditor::AutoListElementCreator::CreateAndUpdateCurrentListElement(
4269 HTMLEditor& aHTMLEditor, const EditorDOMPoint& aPointToInsert,
4270 EmptyListItem aEmptyListItem, AutoHandlingState& aState,
4271 const Element& aEditingHost) const {
4272 MOZ_ASSERT(aPointToInsert.IsSetAndValid());
4274 aState.mPreviousListItemElement = nullptr;
4275 RefPtr<Element> newListItemElement;
4276 auto initializer =
4277 // MOZ_CAN_RUN_SCRIPT_BOUNDARY due to bug 1758868
4278 [&](HTMLEditor&, Element& aListElement, const EditorDOMPoint&)
4279 MOZ_CAN_RUN_SCRIPT_BOUNDARY {
4280 // If the replacing element has `dir` attribute, the new list
4281 // element should take it to correct its list marker position.
4282 if (aState.mReplacingBlockElement) {
4283 nsString dirValue;
4284 if (aState.mReplacingBlockElement->GetAttr(nsGkAtoms::dir,
4285 dirValue) &&
4286 !dirValue.IsEmpty()) {
4287 // We don't need to use transaction to set `dir` attribute here
4288 // because the element will be stored with the `dir` attribute
4289 // in InsertNodeTransaction. Therefore, undo should work.
4290 IgnoredErrorResult ignoredError;
4291 aListElement.SetAttr(nsGkAtoms::dir, dirValue, ignoredError);
4292 NS_WARNING_ASSERTION(
4293 !ignoredError.Failed(),
4294 "Element::SetAttr(nsGkAtoms::dir) failed, but ignored");
4297 if (aEmptyListItem == EmptyListItem::Create) {
4298 Result<CreateElementResult, nsresult> createNewListItemResult =
4299 AppendListItemElement(aHTMLEditor, aListElement, aState);
4300 if (MOZ_UNLIKELY(createNewListItemResult.isErr())) {
4301 NS_WARNING(
4302 "HTMLEditor::AppendNewElementToInsertingElement()"
4303 " failed");
4304 return createNewListItemResult.unwrapErr();
4306 CreateElementResult unwrappedResult =
4307 createNewListItemResult.unwrap();
4308 unwrappedResult.IgnoreCaretPointSuggestion();
4309 newListItemElement = unwrappedResult.UnwrapNewNode();
4311 return NS_OK;
4313 Result<CreateElementResult, nsresult> createNewListElementResult =
4314 aHTMLEditor.InsertElementWithSplittingAncestorsWithTransaction(
4315 mListTagName, aPointToInsert, BRElementNextToSplitPoint::Keep,
4316 aEditingHost, initializer);
4317 if (MOZ_UNLIKELY(createNewListElementResult.isErr())) {
4318 NS_WARNING(
4319 nsPrintfCString(
4320 "HTMLEditor::"
4321 "InsertElementWithSplittingAncestorsWithTransaction(%s) failed",
4322 nsAtomCString(&mListTagName).get())
4323 .get());
4324 return createNewListElementResult.propagateErr();
4326 CreateElementResult unwrappedCreateNewListElementResult =
4327 createNewListElementResult.unwrap();
4328 unwrappedCreateNewListElementResult.IgnoreCaretPointSuggestion();
4330 MOZ_ASSERT(unwrappedCreateNewListElementResult.GetNewNode());
4331 aState.mListOrListItemElementToPutCaret =
4332 newListItemElement ? newListItemElement.get()
4333 : unwrappedCreateNewListElementResult.GetNewNode();
4334 aState.mCurrentListElement =
4335 unwrappedCreateNewListElementResult.UnwrapNewNode();
4336 aState.mPreviousListItemElement = std::move(newListItemElement);
4337 return NS_OK;
4340 // static
4341 nsresult HTMLEditor::AutoListElementCreator::MaybeCloneAttributesToNewListItem(
4342 HTMLEditor& aHTMLEditor, Element& aListItemElement,
4343 AutoHandlingState& aState) {
4344 if (!aState.mReplacingBlockElement) {
4345 return NS_OK;
4347 // If we're replacing a block element, the list items should have attributes
4348 // of the replacing element. However, we don't want to copy `dir` attribute
4349 // because it does not affect content in list item element and setting
4350 // opposite direction from the parent list causes the marker invisible.
4351 // Therefore, we don't want to take it. Finally, we don't need to use
4352 // transaction to copy the attributes here because the element will be stored
4353 // with the attributes in InsertNodeTransaction. Therefore, undo should work.
4354 nsresult rv = aHTMLEditor.CopyAttributes(
4355 WithTransaction::No, aListItemElement,
4356 MOZ_KnownLive(*aState.mReplacingBlockElement),
4357 aState.mMaybeCopiedReplacingBlockElementId
4358 ? HTMLEditor::CopyAllAttributesExceptIdAndDir
4359 : HTMLEditor::CopyAllAttributesExceptDir);
4360 aState.mMaybeCopiedReplacingBlockElementId = true;
4361 if (NS_WARN_IF(aHTMLEditor.Destroyed())) {
4362 return NS_ERROR_EDITOR_DESTROYED;
4364 NS_WARNING_ASSERTION(
4365 NS_SUCCEEDED(rv),
4366 "HTMLEditor::CopyAttributes(WithTransaction::No) failed");
4367 return rv;
4370 Result<CreateElementResult, nsresult>
4371 HTMLEditor::AutoListElementCreator::AppendListItemElement(
4372 HTMLEditor& aHTMLEditor, const Element& aListElement,
4373 AutoHandlingState& aState) const {
4374 const WithTransaction withTransaction = aListElement.IsInComposedDoc()
4375 ? WithTransaction::Yes
4376 : WithTransaction::No;
4377 Result<CreateElementResult, nsresult> createNewListItemResult =
4378 aHTMLEditor.CreateAndInsertElement(
4379 withTransaction, mListItemTagName,
4380 EditorDOMPoint::AtEndOf(aListElement),
4381 !aState.mReplacingBlockElement
4382 ? HTMLEditor::DoNothingForNewElement
4383 // MOZ_CAN_RUN_SCRIPT_BOUNDARY due to bug 1758868
4384 : [&aState](HTMLEditor& aHTMLEditor, Element& aListItemElement,
4385 const EditorDOMPoint&) MOZ_CAN_RUN_SCRIPT_BOUNDARY {
4386 nsresult rv =
4387 AutoListElementCreator::MaybeCloneAttributesToNewListItem(
4388 aHTMLEditor, aListItemElement, aState);
4389 NS_WARNING_ASSERTION(
4390 NS_SUCCEEDED(rv),
4391 "AutoListElementCreator::"
4392 "MaybeCloneAttributesToNewListItem() failed");
4393 return rv;
4395 NS_WARNING_ASSERTION(createNewListItemResult.isOk(),
4396 "HTMLEditor::CreateAndInsertElement() failed");
4397 return createNewListItemResult;
4400 nsresult HTMLEditor::AutoListElementCreator::HandleChildInlineContent(
4401 HTMLEditor& aHTMLEditor, nsIContent& aHandlingInlineContent,
4402 AutoHandlingState& aState) const {
4403 MOZ_ASSERT(HTMLEditUtils::IsInlineContent(
4404 aHandlingInlineContent, BlockInlineCheck::UseHTMLDefaultStyle));
4406 // If we're currently handling contents of a list item and current node
4407 // is not a block element, move current node into the list item.
4408 if (!aState.mPreviousListItemElement) {
4409 nsresult rv = WrapContentIntoNewListItemElement(
4410 aHTMLEditor, aHandlingInlineContent, aState);
4411 NS_WARNING_ASSERTION(
4412 NS_SUCCEEDED(rv),
4413 "AutoListElementCreator::WrapContentIntoNewListItemElement() failed");
4414 return rv;
4417 Result<MoveNodeResult, nsresult> moveInlineElementResult =
4418 aHTMLEditor.MoveNodeToEndWithTransaction(
4419 aHandlingInlineContent,
4420 MOZ_KnownLive(*aState.mPreviousListItemElement));
4421 if (MOZ_UNLIKELY(moveInlineElementResult.isErr())) {
4422 NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed");
4423 return moveInlineElementResult.propagateErr();
4425 moveInlineElementResult.inspect().IgnoreCaretPointSuggestion();
4426 return NS_OK;
4429 nsresult HTMLEditor::AutoListElementCreator::WrapContentIntoNewListItemElement(
4430 HTMLEditor& aHTMLEditor, nsIContent& aHandlingContent,
4431 AutoHandlingState& aState) const {
4432 // If current node is not a paragraph, wrap current node with new list
4433 // item element and move it into current list element.
4434 Result<CreateElementResult, nsresult> wrapContentInListItemElementResult =
4435 aHTMLEditor.InsertContainerWithTransaction(
4436 aHandlingContent, mListItemTagName,
4437 !aState.mReplacingBlockElement
4438 ? HTMLEditor::DoNothingForNewElement
4439 // MOZ_CAN_RUN_SCRIPT_BOUNDARY due to bug 1758868
4440 : [&aState](HTMLEditor& aHTMLEditor, Element& aListItemElement,
4441 const EditorDOMPoint&) MOZ_CAN_RUN_SCRIPT_BOUNDARY {
4442 nsresult rv =
4443 AutoListElementCreator::MaybeCloneAttributesToNewListItem(
4444 aHTMLEditor, aListItemElement, aState);
4445 NS_WARNING_ASSERTION(
4446 NS_SUCCEEDED(rv),
4447 "AutoListElementCreator::"
4448 "MaybeCloneAttributesToNewListItem() failed");
4449 return rv;
4451 if (MOZ_UNLIKELY(wrapContentInListItemElementResult.isErr())) {
4452 NS_WARNING("HTMLEditor::InsertContainerWithTransaction() failed");
4453 return wrapContentInListItemElementResult.unwrapErr();
4455 CreateElementResult unwrappedWrapContentInListItemElementResult =
4456 wrapContentInListItemElementResult.unwrap();
4457 unwrappedWrapContentInListItemElementResult.IgnoreCaretPointSuggestion();
4458 MOZ_ASSERT(unwrappedWrapContentInListItemElementResult.GetNewNode());
4460 // MOZ_KnownLive(unwrappedWrapContentInListItemElementResult.GetNewNode()):
4461 // The result is grabbed by unwrappedWrapContentInListItemElementResult.
4462 Result<MoveNodeResult, nsresult> moveListItemElementResult =
4463 aHTMLEditor.MoveNodeToEndWithTransaction(
4464 MOZ_KnownLive(
4465 *unwrappedWrapContentInListItemElementResult.GetNewNode()),
4466 MOZ_KnownLive(*aState.mCurrentListElement));
4467 if (MOZ_UNLIKELY(moveListItemElementResult.isErr())) {
4468 NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed");
4469 return moveListItemElementResult.unwrapErr();
4471 moveListItemElementResult.inspect().IgnoreCaretPointSuggestion();
4473 // If current node is not a block element, new list item should have
4474 // following inline nodes too.
4475 if (HTMLEditUtils::IsInlineContent(aHandlingContent,
4476 BlockInlineCheck::UseHTMLDefaultStyle)) {
4477 aState.mPreviousListItemElement =
4478 unwrappedWrapContentInListItemElementResult.UnwrapNewNode();
4479 } else {
4480 aState.mPreviousListItemElement = nullptr;
4483 // XXX Why don't we set `type` attribute here??
4484 return NS_OK;
4487 nsresult HTMLEditor::AutoListElementCreator::
4488 EnsureCollapsedRangeIsInListItemOrListElement(
4489 Element& aListItemOrListToPutCaret, AutoRangeArray& aRanges) const {
4490 if (!aRanges.IsCollapsed() || aRanges.Ranges().IsEmpty()) {
4491 return NS_OK;
4494 const auto firstRangeStartPoint =
4495 aRanges.GetFirstRangeStartPoint<EditorRawDOMPoint>();
4496 if (MOZ_UNLIKELY(!firstRangeStartPoint.IsSet())) {
4497 return NS_OK;
4499 Result<EditorRawDOMPoint, nsresult> pointToPutCaretOrError =
4500 HTMLEditUtils::ComputePointToPutCaretInElementIfOutside<
4501 EditorRawDOMPoint>(aListItemOrListToPutCaret, firstRangeStartPoint);
4502 if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) {
4503 NS_WARNING("HTMLEditUtils::ComputePointToPutCaretInElementIfOutside()");
4504 return pointToPutCaretOrError.unwrapErr();
4506 if (pointToPutCaretOrError.inspect().IsSet()) {
4507 nsresult rv = aRanges.Collapse(pointToPutCaretOrError.inspect());
4508 if (NS_FAILED(rv)) {
4509 NS_WARNING("AutoRangeArray::Collapse() failed");
4510 return rv;
4513 return NS_OK;
4516 nsresult HTMLEditor::RemoveListAtSelectionAsSubAction(
4517 const Element& aEditingHost) {
4518 MOZ_ASSERT(IsEditActionDataAvailable());
4521 Result<EditActionResult, nsresult> result = CanHandleHTMLEditSubAction();
4522 if (MOZ_UNLIKELY(result.isErr())) {
4523 NS_WARNING("HTMLEditor::CanHandleHTMLEditSubAction() failed");
4524 return result.unwrapErr();
4526 if (result.inspect().Canceled()) {
4527 return NS_OK;
4531 AutoPlaceholderBatch treatAsOneTransaction(
4532 *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
4533 IgnoredErrorResult error;
4534 AutoEditSubActionNotifier startToHandleEditSubAction(
4535 *this, EditSubAction::eRemoveList, nsIEditor::eNext, error);
4536 if (NS_WARN_IF(error.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
4537 return error.StealNSResult();
4539 NS_WARNING_ASSERTION(
4540 !error.Failed(),
4541 "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
4543 // XXX Why do we do this only when there is only one selection range?
4544 if (!SelectionRef().IsCollapsed() && SelectionRef().RangeCount() == 1u) {
4545 Result<EditorRawDOMRange, nsresult> extendedRange =
4546 GetRangeExtendedToHardLineEdgesForBlockEditAction(
4547 SelectionRef().GetRangeAt(0u), aEditingHost);
4548 if (MOZ_UNLIKELY(extendedRange.isErr())) {
4549 NS_WARNING(
4550 "HTMLEditor::GetRangeExtendedToHardLineEdgesForBlockEditAction() "
4551 "failed");
4552 return extendedRange.unwrapErr();
4554 // Note that end point may be prior to start point. So, we
4555 // cannot use Selection::SetStartAndEndInLimit() here.
4556 error.SuppressException();
4557 SelectionRef().SetBaseAndExtentInLimiter(
4558 extendedRange.inspect().StartRef().ToRawRangeBoundary(),
4559 extendedRange.inspect().EndRef().ToRawRangeBoundary(), error);
4560 if (NS_WARN_IF(Destroyed())) {
4561 return NS_ERROR_EDITOR_DESTROYED;
4563 if (error.Failed()) {
4564 NS_WARNING("Selection::SetBaseAndExtentInLimiter() failed");
4565 return error.StealNSResult();
4569 AutoSelectionRestorer restoreSelectionLater(this);
4571 AutoTArray<OwningNonNull<nsIContent>, 64> arrayOfContents;
4573 // TODO: We don't need AutoTransactionsConserveSelection here in the normal
4574 // cases, but removing this may cause the behavior with the legacy
4575 // mutation event listeners. We should try to delete this in a bug.
4576 AutoTransactionsConserveSelection dontChangeMySelection(*this);
4579 AutoRangeArray extendedSelectionRanges(SelectionRef());
4580 extendedSelectionRanges.ExtendRangesToWrapLines(
4581 EditSubAction::eCreateOrChangeList,
4582 BlockInlineCheck::UseHTMLDefaultStyle, aEditingHost);
4583 Result<EditorDOMPoint, nsresult> splitResult =
4584 extendedSelectionRanges
4585 .SplitTextAtEndBoundariesAndInlineAncestorsAtBothBoundaries(
4586 *this, BlockInlineCheck::UseHTMLDefaultStyle, aEditingHost);
4587 if (MOZ_UNLIKELY(splitResult.isErr())) {
4588 NS_WARNING(
4589 "AutoRangeArray::"
4590 "SplitTextAtEndBoundariesAndInlineAncestorsAtBothBoundaries() "
4591 "failed");
4592 return splitResult.unwrapErr();
4594 nsresult rv = extendedSelectionRanges.CollectEditTargetNodes(
4595 *this, arrayOfContents, EditSubAction::eCreateOrChangeList,
4596 AutoRangeArray::CollectNonEditableNodes::No);
4597 if (NS_FAILED(rv)) {
4598 NS_WARNING(
4599 "AutoRangeArray::CollectEditTargetNodes(EditSubAction::"
4600 "eCreateOrChangeList, CollectNonEditableNodes::No) failed");
4601 return rv;
4605 const Result<EditorDOMPoint, nsresult> splitAtBRElementsResult =
4606 MaybeSplitElementsAtEveryBRElement(arrayOfContents,
4607 EditSubAction::eCreateOrChangeList);
4608 if (MOZ_UNLIKELY(splitAtBRElementsResult.isErr())) {
4609 NS_WARNING(
4610 "HTMLEditor::MaybeSplitElementsAtEveryBRElement(EditSubAction::"
4611 "eCreateOrChangeList) failed");
4612 return splitAtBRElementsResult.inspectErr();
4616 // Remove all non-editable nodes. Leave them be.
4617 // XXX CollectEditTargetNodes() should return only editable contents when it's
4618 // called with CollectNonEditableNodes::No, but checking it here, looks
4619 // like just wasting the runtime cost.
4620 for (int32_t i = arrayOfContents.Length() - 1; i >= 0; i--) {
4621 OwningNonNull<nsIContent>& content = arrayOfContents[i];
4622 if (!EditorUtils::IsEditableContent(content, EditorType::HTML)) {
4623 arrayOfContents.RemoveElementAt(i);
4627 // Only act on lists or list items in the array
4628 for (auto& content : arrayOfContents) {
4629 // here's where we actually figure out what to do
4630 if (HTMLEditUtils::IsListItem(content)) {
4631 // unlist this listitem
4632 nsresult rv = LiftUpListItemElement(MOZ_KnownLive(*content->AsElement()),
4633 LiftUpFromAllParentListElements::Yes);
4634 if (NS_FAILED(rv)) {
4635 NS_WARNING(
4636 "HTMLEditor::LiftUpListItemElement(LiftUpFromAllParentListElements:"
4637 ":Yes) failed");
4638 return rv;
4640 continue;
4642 if (HTMLEditUtils::IsAnyListElement(content)) {
4643 // node is a list, move list items out
4644 nsresult rv =
4645 DestroyListStructureRecursively(MOZ_KnownLive(*content->AsElement()));
4646 if (NS_FAILED(rv)) {
4647 NS_WARNING("HTMLEditor::DestroyListStructureRecursively() failed");
4648 return rv;
4650 continue;
4653 return NS_OK;
4656 Result<RefPtr<Element>, nsresult>
4657 HTMLEditor::FormatBlockContainerWithTransaction(
4658 AutoRangeArray& aSelectionRanges, const nsStaticAtom& aNewFormatTagName,
4659 FormatBlockMode aFormatBlockMode, const Element& aEditingHost) {
4660 MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable());
4662 // XXX Why do we do this only when there is only one selection range?
4663 if (!aSelectionRanges.IsCollapsed() &&
4664 aSelectionRanges.Ranges().Length() == 1u) {
4665 Result<EditorRawDOMRange, nsresult> extendedRange =
4666 GetRangeExtendedToHardLineEdgesForBlockEditAction(
4667 aSelectionRanges.FirstRangeRef(), aEditingHost);
4668 if (MOZ_UNLIKELY(extendedRange.isErr())) {
4669 NS_WARNING(
4670 "HTMLEditor::GetRangeExtendedToHardLineEdgesForBlockEditAction() "
4671 "failed");
4672 return extendedRange.propagateErr();
4674 // Note that end point may be prior to start point. So, we
4675 // cannot use AutoRangeArray::SetStartAndEnd() here.
4676 if (NS_FAILED(aSelectionRanges.SetBaseAndExtent(
4677 extendedRange.inspect().StartRef(),
4678 extendedRange.inspect().EndRef()))) {
4679 NS_WARNING("AutoRangeArray::SetBaseAndExtent() failed");
4680 return Err(NS_ERROR_FAILURE);
4684 MOZ_ALWAYS_TRUE(aSelectionRanges.SaveAndTrackRanges(*this));
4686 // TODO: We don't need AutoTransactionsConserveSelection here in the normal
4687 // cases, but removing this may cause the behavior with the legacy
4688 // mutation event listeners. We should try to delete this in a bug.
4689 AutoTransactionsConserveSelection dontChangeMySelection(*this);
4691 AutoTArray<OwningNonNull<nsIContent>, 64> arrayOfContents;
4692 aSelectionRanges.ExtendRangesToWrapLines(
4693 aFormatBlockMode == FormatBlockMode::HTMLFormatBlockCommand
4694 ? EditSubAction::eFormatBlockForHTMLCommand
4695 : EditSubAction::eCreateOrRemoveBlock,
4696 BlockInlineCheck::UseComputedDisplayOutsideStyle, aEditingHost);
4697 Result<EditorDOMPoint, nsresult> splitResult =
4698 aSelectionRanges
4699 .SplitTextAtEndBoundariesAndInlineAncestorsAtBothBoundaries(
4700 *this, BlockInlineCheck::UseComputedDisplayOutsideStyle,
4701 aEditingHost);
4702 if (MOZ_UNLIKELY(splitResult.isErr())) {
4703 NS_WARNING(
4704 "AutoRangeArray::"
4705 "SplitTextAtEndBoundariesAndInlineAncestorsAtBothBoundaries() failed");
4706 return splitResult.propagateErr();
4708 nsresult rv = aSelectionRanges.CollectEditTargetNodes(
4709 *this, arrayOfContents,
4710 aFormatBlockMode == FormatBlockMode::HTMLFormatBlockCommand
4711 ? EditSubAction::eFormatBlockForHTMLCommand
4712 : EditSubAction::eCreateOrRemoveBlock,
4713 AutoRangeArray::CollectNonEditableNodes::Yes);
4714 if (NS_FAILED(rv)) {
4715 NS_WARNING(
4716 "AutoRangeArray::CollectEditTargetNodes(CollectNonEditableNodes::No) "
4717 "failed");
4718 return Err(rv);
4721 Result<EditorDOMPoint, nsresult> splitAtBRElementsResult =
4722 MaybeSplitElementsAtEveryBRElement(
4723 arrayOfContents,
4724 aFormatBlockMode == FormatBlockMode::HTMLFormatBlockCommand
4725 ? EditSubAction::eFormatBlockForHTMLCommand
4726 : EditSubAction::eCreateOrRemoveBlock);
4727 if (MOZ_UNLIKELY(splitAtBRElementsResult.isErr())) {
4728 NS_WARNING("HTMLEditor::MaybeSplitElementsAtEveryBRElement() failed");
4729 return splitAtBRElementsResult.propagateErr();
4732 // If there is no visible and editable nodes in the edit targets, make an
4733 // empty block.
4734 // XXX Isn't this odd if there are only non-editable visible nodes?
4735 if (HTMLEditUtils::IsEmptyOneHardLine(
4736 arrayOfContents, BlockInlineCheck::UseComputedDisplayOutsideStyle)) {
4737 if (NS_WARN_IF(aSelectionRanges.Ranges().IsEmpty())) {
4738 return Err(NS_ERROR_FAILURE);
4741 auto pointToInsertBlock =
4742 aSelectionRanges.GetFirstRangeStartPoint<EditorDOMPoint>();
4743 if (aFormatBlockMode == FormatBlockMode::XULParagraphStateCommand &&
4744 (&aNewFormatTagName == nsGkAtoms::normal ||
4745 &aNewFormatTagName == nsGkAtoms::_empty)) {
4746 if (!pointToInsertBlock.IsInContentNode()) {
4747 NS_WARNING(
4748 "HTMLEditor::FormatBlockContainerWithTransaction() couldn't find "
4749 "block parent because container of the point is not content");
4750 return Err(NS_ERROR_FAILURE);
4752 // We are removing blocks (going to "body text")
4753 const RefPtr<Element> editableBlockElement =
4754 HTMLEditUtils::GetInclusiveAncestorElement(
4755 *pointToInsertBlock.ContainerAs<nsIContent>(),
4756 HTMLEditUtils::ClosestEditableBlockElement,
4757 BlockInlineCheck::UseComputedDisplayOutsideStyle);
4758 if (!editableBlockElement) {
4759 NS_WARNING(
4760 "HTMLEditor::FormatBlockContainerWithTransaction() couldn't find "
4761 "block parent");
4762 return Err(NS_ERROR_FAILURE);
4764 if (editableBlockElement->IsAnyOfHTMLElements(
4765 nsGkAtoms::dd, nsGkAtoms::dl, nsGkAtoms::dt) ||
4766 !HTMLEditUtils::IsFormatElementForParagraphStateCommand(
4767 *editableBlockElement)) {
4768 return RefPtr<Element>();
4771 // If the first editable node after selection is a br, consume it.
4772 // Otherwise it gets pushed into a following block after the split,
4773 // which is visually bad.
4774 if (nsCOMPtr<nsIContent> brContent = HTMLEditUtils::GetNextContent(
4775 pointToInsertBlock, {WalkTreeOption::IgnoreNonEditableNode},
4776 BlockInlineCheck::UseComputedDisplayOutsideStyle,
4777 &aEditingHost)) {
4778 if (brContent && brContent->IsHTMLElement(nsGkAtoms::br)) {
4779 AutoEditorDOMPointChildInvalidator lockOffset(pointToInsertBlock);
4780 nsresult rv = DeleteNodeWithTransaction(*brContent);
4781 if (NS_FAILED(rv)) {
4782 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
4783 return Err(rv);
4787 // Do the splits!
4788 Result<SplitNodeResult, nsresult> splitNodeResult =
4789 SplitNodeDeepWithTransaction(
4790 *editableBlockElement, pointToInsertBlock,
4791 SplitAtEdges::eDoNotCreateEmptyContainer);
4792 if (MOZ_UNLIKELY(splitNodeResult.isErr())) {
4793 NS_WARNING("HTMLEditor::SplitNodeDeepWithTransaction() failed");
4794 return splitNodeResult.propagateErr();
4796 SplitNodeResult unwrappedSplitNodeResult = splitNodeResult.unwrap();
4797 unwrappedSplitNodeResult.IgnoreCaretPointSuggestion();
4798 // Put a <br> element at the split point
4799 Result<CreateElementResult, nsresult> insertBRElementResult =
4800 InsertBRElement(
4801 WithTransaction::Yes,
4802 unwrappedSplitNodeResult.AtSplitPoint<EditorDOMPoint>());
4803 if (MOZ_UNLIKELY(insertBRElementResult.isErr())) {
4804 NS_WARNING("HTMLEditor::InsertBRElement(WithTransaction::Yes) failed");
4805 return insertBRElementResult.propagateErr();
4807 MOZ_ASSERT(insertBRElementResult.inspect().GetNewNode());
4808 aSelectionRanges.ClearSavedRanges();
4809 nsresult rv = aSelectionRanges.Collapse(
4810 EditorRawDOMPoint(insertBRElementResult.inspect().GetNewNode()));
4811 if (NS_FAILED(rv)) {
4812 NS_WARNING("AutoRangeArray::Collapse() failed");
4813 return Err(rv);
4815 return RefPtr<Element>();
4818 // We are making a block. Consume a br, if needed.
4819 if (nsCOMPtr<nsIContent> maybeBRContent = HTMLEditUtils::GetNextContent(
4820 pointToInsertBlock,
4821 {WalkTreeOption::IgnoreNonEditableNode,
4822 WalkTreeOption::StopAtBlockBoundary},
4823 BlockInlineCheck::UseComputedDisplayOutsideStyle, &aEditingHost)) {
4824 if (maybeBRContent->IsHTMLElement(nsGkAtoms::br)) {
4825 AutoEditorDOMPointChildInvalidator lockOffset(pointToInsertBlock);
4826 nsresult rv = DeleteNodeWithTransaction(*maybeBRContent);
4827 if (NS_FAILED(rv)) {
4828 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
4829 return Err(rv);
4831 // We don't need to act on this node any more
4832 arrayOfContents.RemoveElement(maybeBRContent);
4835 // Make sure we can put a block here.
4836 Result<CreateElementResult, nsresult> createNewBlockElementResult =
4837 InsertElementWithSplittingAncestorsWithTransaction(
4838 aNewFormatTagName, pointToInsertBlock,
4839 BRElementNextToSplitPoint::Keep, aEditingHost);
4840 if (MOZ_UNLIKELY(createNewBlockElementResult.isErr())) {
4841 NS_WARNING(
4842 nsPrintfCString(
4843 "HTMLEditor::InsertElementWithSplittingAncestorsWithTransaction("
4844 "%s) failed",
4845 nsAtomCString(&aNewFormatTagName).get())
4846 .get());
4847 return createNewBlockElementResult.propagateErr();
4849 CreateElementResult unwrappedCreateNewBlockElementResult =
4850 createNewBlockElementResult.unwrap();
4851 unwrappedCreateNewBlockElementResult.IgnoreCaretPointSuggestion();
4852 MOZ_ASSERT(unwrappedCreateNewBlockElementResult.GetNewNode());
4854 // Delete anything that was in the list of nodes
4855 while (!arrayOfContents.IsEmpty()) {
4856 OwningNonNull<nsIContent>& content = arrayOfContents[0];
4857 // MOZ_KnownLive because 'arrayOfContents' is guaranteed to
4858 // keep it alive.
4859 nsresult rv = DeleteNodeWithTransaction(MOZ_KnownLive(*content));
4860 if (NS_FAILED(rv)) {
4861 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
4862 return Err(rv);
4864 arrayOfContents.RemoveElementAt(0);
4866 // Put selection in new block
4867 aSelectionRanges.ClearSavedRanges();
4868 nsresult rv = aSelectionRanges.Collapse(EditorRawDOMPoint(
4869 unwrappedCreateNewBlockElementResult.GetNewNode(), 0u));
4870 if (NS_FAILED(rv)) {
4871 NS_WARNING("AutoRangeArray::Collapse() failed");
4872 return Err(rv);
4874 return unwrappedCreateNewBlockElementResult.UnwrapNewNode();
4877 if (aFormatBlockMode == FormatBlockMode::XULParagraphStateCommand) {
4878 // Okay, now go through all the nodes and make the right kind of blocks, or
4879 // whatever is appropriate.
4880 // Note: blockquote is handled a little differently.
4881 if (&aNewFormatTagName == nsGkAtoms::blockquote) {
4882 Result<CreateElementResult, nsresult>
4883 wrapContentsInBlockquoteElementsResult =
4884 WrapContentsInBlockquoteElementsWithTransaction(arrayOfContents,
4885 aEditingHost);
4886 if (MOZ_UNLIKELY(wrapContentsInBlockquoteElementsResult.isErr())) {
4887 NS_WARNING(
4888 "HTMLEditor::WrapContentsInBlockquoteElementsWithTransaction() "
4889 "failed");
4890 return wrapContentsInBlockquoteElementsResult.propagateErr();
4892 wrapContentsInBlockquoteElementsResult.inspect()
4893 .IgnoreCaretPointSuggestion();
4894 return wrapContentsInBlockquoteElementsResult.unwrap().UnwrapNewNode();
4896 if (&aNewFormatTagName == nsGkAtoms::normal ||
4897 &aNewFormatTagName == nsGkAtoms::_empty) {
4898 Result<EditorDOMPoint, nsresult> removeBlockContainerElementsResult =
4899 RemoveBlockContainerElementsWithTransaction(
4900 arrayOfContents, FormatBlockMode::XULParagraphStateCommand,
4901 BlockInlineCheck::UseComputedDisplayOutsideStyle);
4902 if (MOZ_UNLIKELY(removeBlockContainerElementsResult.isErr())) {
4903 NS_WARNING(
4904 "HTMLEditor::RemoveBlockContainerElementsWithTransaction() failed");
4905 return removeBlockContainerElementsResult.propagateErr();
4907 return RefPtr<Element>();
4911 Result<CreateElementResult, nsresult> wrapContentsInBlockElementResult =
4912 CreateOrChangeFormatContainerElement(arrayOfContents, aNewFormatTagName,
4913 aFormatBlockMode, aEditingHost);
4914 if (MOZ_UNLIKELY(wrapContentsInBlockElementResult.isErr())) {
4915 NS_WARNING("HTMLEditor::CreateOrChangeFormatContainerElement() failed");
4916 return wrapContentsInBlockElementResult.propagateErr();
4918 wrapContentsInBlockElementResult.inspect().IgnoreCaretPointSuggestion();
4919 return wrapContentsInBlockElementResult.unwrap().UnwrapNewNode();
4922 nsresult HTMLEditor::MaybeInsertPaddingBRElementForEmptyLastLineAtSelection() {
4923 MOZ_ASSERT(IsEditActionDataAvailable());
4924 MOZ_ASSERT(!IsSelectionRangeContainerNotContent());
4926 if (!SelectionRef().IsCollapsed()) {
4927 return NS_OK;
4930 const nsRange* firstRange = SelectionRef().GetRangeAt(0);
4931 if (NS_WARN_IF(!firstRange)) {
4932 return NS_ERROR_FAILURE;
4934 const RangeBoundary& atStartOfSelection = firstRange->StartRef();
4935 if (NS_WARN_IF(!atStartOfSelection.IsSet())) {
4936 return NS_ERROR_FAILURE;
4938 if (!atStartOfSelection.Container()->IsElement()) {
4939 return NS_OK;
4941 OwningNonNull<Element> startContainerElement =
4942 *atStartOfSelection.Container()->AsElement();
4943 nsresult rv =
4944 InsertPaddingBRElementForEmptyLastLineIfNeeded(startContainerElement);
4945 NS_WARNING_ASSERTION(
4946 NS_SUCCEEDED(rv),
4947 "HTMLEditor::InsertPaddingBRElementForEmptyLastLineIfNeeded() failed");
4948 return rv;
4951 Result<EditActionResult, nsresult> HTMLEditor::IndentAsSubAction(
4952 const Element& aEditingHost) {
4953 MOZ_ASSERT(IsEditActionDataAvailable());
4955 AutoPlaceholderBatch treatAsOneTransaction(
4956 *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
4957 IgnoredErrorResult ignoredError;
4958 AutoEditSubActionNotifier startToHandleEditSubAction(
4959 *this, EditSubAction::eIndent, nsIEditor::eNext, ignoredError);
4960 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
4961 return Err(ignoredError.StealNSResult());
4963 NS_WARNING_ASSERTION(
4964 !ignoredError.Failed(),
4965 "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
4968 Result<EditActionResult, nsresult> result = CanHandleHTMLEditSubAction();
4969 if (MOZ_UNLIKELY(result.isErr())) {
4970 NS_WARNING("HTMLEditor::CanHandleHTMLEditSubAction() failed");
4971 return result;
4973 if (result.inspect().Canceled()) {
4974 return result;
4978 if (MOZ_UNLIKELY(IsSelectionRangeContainerNotContent())) {
4979 NS_WARNING("Some selection containers are not content node, but ignored");
4980 return EditActionResult::IgnoredResult();
4983 Result<EditActionResult, nsresult> result =
4984 HandleIndentAtSelection(aEditingHost);
4985 if (MOZ_UNLIKELY(result.isErr())) {
4986 NS_WARNING("HTMLEditor::HandleIndentAtSelection() failed");
4987 return result;
4989 if (result.inspect().Canceled()) {
4990 return result;
4993 if (MOZ_UNLIKELY(IsSelectionRangeContainerNotContent())) {
4994 NS_WARNING("Mutation event listener might have changed selection");
4995 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
4998 // TODO: Investigate when we need to put a `<br>` element after indenting
4999 // ranges. Then, we could stop calling this here, or maybe we need to
5000 // do it while moving content nodes.
5001 nsresult rv = MaybeInsertPaddingBRElementForEmptyLastLineAtSelection();
5002 if (NS_FAILED(rv)) {
5003 NS_WARNING(
5004 "MaybeInsertPaddingBRElementForEmptyLastLineAtSelection() failed");
5005 return Err(rv);
5007 return result;
5010 Result<EditorDOMPoint, nsresult> HTMLEditor::IndentListChildWithTransaction(
5011 RefPtr<Element>* aSubListElement, const EditorDOMPoint& aPointInListElement,
5012 nsIContent& aContentMovingToSubList, const Element& aEditingHost) {
5013 MOZ_ASSERT(
5014 HTMLEditUtils::IsAnyListElement(aPointInListElement.GetContainer()),
5015 "unexpected container");
5016 MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable());
5018 // some logic for putting list items into nested lists...
5020 // If aContentMovingToSubList is followed by a sub-list element whose tag is
5021 // same as the parent list element's tag, we can move it to start of the
5022 // sub-list.
5023 if (nsIContent* nextEditableSibling = HTMLEditUtils::GetNextSibling(
5024 aContentMovingToSubList, {WalkTreeOption::IgnoreWhiteSpaceOnlyText,
5025 WalkTreeOption::IgnoreNonEditableNode})) {
5026 if (HTMLEditUtils::IsAnyListElement(nextEditableSibling) &&
5027 aPointInListElement.GetContainer()->NodeInfo()->NameAtom() ==
5028 nextEditableSibling->NodeInfo()->NameAtom() &&
5029 aPointInListElement.GetContainer()->NodeInfo()->NamespaceID() ==
5030 nextEditableSibling->NodeInfo()->NamespaceID()) {
5031 Result<MoveNodeResult, nsresult> moveListElementResult =
5032 MoveNodeWithTransaction(aContentMovingToSubList,
5033 EditorDOMPoint(nextEditableSibling, 0u));
5034 if (MOZ_UNLIKELY(moveListElementResult.isErr())) {
5035 NS_WARNING("HTMLEditor::MoveNodeWithTransaction() failed");
5036 return moveListElementResult.propagateErr();
5038 return moveListElementResult.unwrap().UnwrapCaretPoint();
5042 // If aContentMovingToSubList follows a sub-list element whose tag is same
5043 // as the parent list element's tag, we can move it to end of the sub-list.
5044 if (nsCOMPtr<nsIContent> previousEditableSibling =
5045 HTMLEditUtils::GetPreviousSibling(
5046 aContentMovingToSubList,
5047 {WalkTreeOption::IgnoreWhiteSpaceOnlyText,
5048 WalkTreeOption::IgnoreNonEditableNode})) {
5049 if (HTMLEditUtils::IsAnyListElement(previousEditableSibling) &&
5050 aPointInListElement.GetContainer()->NodeInfo()->NameAtom() ==
5051 previousEditableSibling->NodeInfo()->NameAtom() &&
5052 aPointInListElement.GetContainer()->NodeInfo()->NamespaceID() ==
5053 previousEditableSibling->NodeInfo()->NamespaceID()) {
5054 Result<MoveNodeResult, nsresult> moveListElementResult =
5055 MoveNodeToEndWithTransaction(aContentMovingToSubList,
5056 *previousEditableSibling);
5057 if (MOZ_UNLIKELY(moveListElementResult.isErr())) {
5058 NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed");
5059 return moveListElementResult.propagateErr();
5061 return moveListElementResult.unwrap().UnwrapCaretPoint();
5065 // If aContentMovingToSubList does not follow aSubListElement, we need
5066 // to create new sub-list element.
5067 EditorDOMPoint pointToPutCaret;
5068 nsIContent* previousEditableSibling =
5069 *aSubListElement ? HTMLEditUtils::GetPreviousSibling(
5070 aContentMovingToSubList,
5071 {WalkTreeOption::IgnoreWhiteSpaceOnlyText,
5072 WalkTreeOption::IgnoreNonEditableNode})
5073 : nullptr;
5074 if (!*aSubListElement || (previousEditableSibling &&
5075 previousEditableSibling != *aSubListElement)) {
5076 nsAtom* containerName =
5077 aPointInListElement.GetContainer()->NodeInfo()->NameAtom();
5078 // Create a new nested list of correct type.
5079 Result<CreateElementResult, nsresult> createNewListElementResult =
5080 InsertElementWithSplittingAncestorsWithTransaction(
5081 MOZ_KnownLive(*containerName), aPointInListElement,
5082 BRElementNextToSplitPoint::Keep, aEditingHost);
5083 if (MOZ_UNLIKELY(createNewListElementResult.isErr())) {
5084 NS_WARNING(
5085 nsPrintfCString(
5086 "HTMLEditor::InsertElementWithSplittingAncestorsWithTransaction("
5087 "%s) failed",
5088 nsAtomCString(containerName).get())
5089 .get());
5090 return createNewListElementResult.propagateErr();
5092 CreateElementResult unwrappedCreateNewListElementResult =
5093 createNewListElementResult.unwrap();
5094 MOZ_ASSERT(unwrappedCreateNewListElementResult.GetNewNode());
5095 pointToPutCaret = unwrappedCreateNewListElementResult.UnwrapCaretPoint();
5096 *aSubListElement = unwrappedCreateNewListElementResult.UnwrapNewNode();
5099 // Finally, we should move aContentMovingToSubList into aSubListElement.
5100 const RefPtr<Element> subListElement = *aSubListElement;
5101 Result<MoveNodeResult, nsresult> moveNodeResult =
5102 MoveNodeToEndWithTransaction(aContentMovingToSubList, *subListElement);
5103 if (MOZ_UNLIKELY(moveNodeResult.isErr())) {
5104 NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed");
5105 return moveNodeResult.propagateErr();
5107 MoveNodeResult unwrappedMoveNodeResult = moveNodeResult.unwrap();
5108 if (unwrappedMoveNodeResult.HasCaretPointSuggestion()) {
5109 pointToPutCaret = unwrappedMoveNodeResult.UnwrapCaretPoint();
5111 return pointToPutCaret;
5114 Result<EditActionResult, nsresult> HTMLEditor::HandleIndentAtSelection(
5115 const Element& aEditingHost) {
5116 MOZ_ASSERT(IsEditActionDataAvailable());
5117 MOZ_ASSERT(!IsSelectionRangeContainerNotContent());
5119 nsresult rv = EnsureNoPaddingBRElementForEmptyEditor();
5120 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
5121 return Err(NS_ERROR_EDITOR_DESTROYED);
5123 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
5124 "EditorBase::EnsureNoPaddingBRElementForEmptyEditor() "
5125 "failed, but ignored");
5127 if (NS_SUCCEEDED(rv) && SelectionRef().IsCollapsed()) {
5128 nsresult rv = EnsureCaretNotAfterInvisibleBRElement();
5129 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
5130 return Err(NS_ERROR_EDITOR_DESTROYED);
5132 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
5133 "HTMLEditor::EnsureCaretNotAfterInvisibleBRElement() "
5134 "failed, but ignored");
5135 if (NS_SUCCEEDED(rv)) {
5136 nsresult rv = PrepareInlineStylesForCaret();
5137 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
5138 return Err(NS_ERROR_EDITOR_DESTROYED);
5140 NS_WARNING_ASSERTION(
5141 NS_SUCCEEDED(rv),
5142 "HTMLEditor::PrepareInlineStylesForCaret() failed, but ignored");
5146 AutoRangeArray selectionRanges(SelectionRef());
5148 if (MOZ_UNLIKELY(!selectionRanges.IsInContent())) {
5149 NS_WARNING("Mutation event listener might have changed the selection");
5150 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
5153 if (IsCSSEnabled()) {
5154 nsresult rv = HandleCSSIndentAroundRanges(selectionRanges, aEditingHost);
5155 if (NS_FAILED(rv)) {
5156 NS_WARNING("HTMLEditor::HandleCSSIndentAroundRanges() failed");
5157 return Err(rv);
5159 } else {
5160 nsresult rv = HandleHTMLIndentAroundRanges(selectionRanges, aEditingHost);
5161 if (NS_FAILED(rv)) {
5162 NS_WARNING("HTMLEditor::HandleHTMLIndentAroundRanges() failed");
5163 return Err(rv);
5166 rv = selectionRanges.ApplyTo(SelectionRef());
5167 if (MOZ_UNLIKELY(Destroyed())) {
5168 NS_WARNING("AutoRangeArray::ApplyTo() caused destroying the editor");
5169 return Err(NS_ERROR_EDITOR_DESTROYED);
5171 if (NS_FAILED(rv)) {
5172 NS_WARNING("AutoRangeArray::ApplyTo() failed");
5173 return Err(rv);
5175 return EditActionResult::HandledResult();
5178 nsresult HTMLEditor::HandleCSSIndentAroundRanges(AutoRangeArray& aRanges,
5179 const Element& aEditingHost) {
5180 MOZ_ASSERT(IsEditActionDataAvailable());
5181 MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable());
5182 MOZ_ASSERT(!aRanges.Ranges().IsEmpty());
5183 MOZ_ASSERT(aRanges.IsInContent());
5185 if (aRanges.Ranges().IsEmpty()) {
5186 NS_WARNING("There is no selection range");
5187 return NS_ERROR_FAILURE;
5190 // XXX Why do we do this only when there is only one selection range?
5191 if (!aRanges.IsCollapsed() && aRanges.Ranges().Length() == 1u) {
5192 Result<EditorRawDOMRange, nsresult> extendedRange =
5193 GetRangeExtendedToHardLineEdgesForBlockEditAction(
5194 aRanges.FirstRangeRef(), aEditingHost);
5195 if (MOZ_UNLIKELY(extendedRange.isErr())) {
5196 NS_WARNING(
5197 "HTMLEditor::GetRangeExtendedToHardLineEdgesForBlockEditAction() "
5198 "failed");
5199 return extendedRange.unwrapErr();
5201 // Note that end point may be prior to start point. So, we
5202 // cannot use SetStartAndEnd() here.
5203 nsresult rv = aRanges.SetBaseAndExtent(extendedRange.inspect().StartRef(),
5204 extendedRange.inspect().EndRef());
5205 if (NS_FAILED(rv)) {
5206 NS_WARNING("AutoRangeArray::SetBaseAndExtent() failed");
5207 return rv;
5211 if (NS_WARN_IF(!aRanges.SaveAndTrackRanges(*this))) {
5212 return NS_ERROR_FAILURE;
5215 AutoTArray<OwningNonNull<nsIContent>, 64> arrayOfContents;
5217 // short circuit: detect case of collapsed selection inside an <li>.
5218 // just sublist that <li>. This prevents bug 97797.
5220 if (aRanges.IsCollapsed()) {
5221 const auto atCaret = aRanges.GetFirstRangeStartPoint<EditorRawDOMPoint>();
5222 if (NS_WARN_IF(!atCaret.IsSet())) {
5223 return NS_ERROR_FAILURE;
5225 MOZ_ASSERT(atCaret.IsInContentNode());
5226 Element* const editableBlockElement =
5227 HTMLEditUtils::GetInclusiveAncestorElement(
5228 *atCaret.ContainerAs<nsIContent>(),
5229 HTMLEditUtils::ClosestEditableBlockElement,
5230 BlockInlineCheck::UseHTMLDefaultStyle);
5231 if (editableBlockElement &&
5232 HTMLEditUtils::IsListItem(editableBlockElement)) {
5233 arrayOfContents.AppendElement(*editableBlockElement);
5237 EditorDOMPoint pointToPutCaret;
5238 if (arrayOfContents.IsEmpty()) {
5240 AutoRangeArray extendedRanges(aRanges);
5241 extendedRanges.ExtendRangesToWrapLines(
5242 EditSubAction::eIndent, BlockInlineCheck::UseHTMLDefaultStyle,
5243 aEditingHost);
5244 Result<EditorDOMPoint, nsresult> splitResult =
5245 extendedRanges
5246 .SplitTextAtEndBoundariesAndInlineAncestorsAtBothBoundaries(
5247 *this, BlockInlineCheck::UseHTMLDefaultStyle, aEditingHost);
5248 if (MOZ_UNLIKELY(splitResult.isErr())) {
5249 NS_WARNING(
5250 "AutoRangeArray::"
5251 "SplitTextAtEndBoundariesAndInlineAncestorsAtBothBoundaries() "
5252 "failed");
5253 return splitResult.unwrapErr();
5255 if (splitResult.inspect().IsSet()) {
5256 pointToPutCaret = splitResult.unwrap();
5258 nsresult rv = extendedRanges.CollectEditTargetNodes(
5259 *this, arrayOfContents, EditSubAction::eIndent,
5260 AutoRangeArray::CollectNonEditableNodes::Yes);
5261 if (NS_FAILED(rv)) {
5262 NS_WARNING(
5263 "AutoRangeArray::CollectEditTargetNodes(EditSubAction::eIndent, "
5264 "CollectNonEditableNodes::Yes) failed");
5265 return rv;
5268 Result<EditorDOMPoint, nsresult> splitAtBRElementsResult =
5269 MaybeSplitElementsAtEveryBRElement(arrayOfContents,
5270 EditSubAction::eIndent);
5271 if (MOZ_UNLIKELY(splitAtBRElementsResult.isErr())) {
5272 NS_WARNING(
5273 "HTMLEditor::MaybeSplitElementsAtEveryBRElement(EditSubAction::"
5274 "eIndent) failed");
5275 return splitAtBRElementsResult.inspectErr();
5277 if (splitAtBRElementsResult.inspect().IsSet()) {
5278 pointToPutCaret = splitAtBRElementsResult.unwrap();
5282 // If there is no visible and editable nodes in the edit targets, make an
5283 // empty block.
5284 // XXX Isn't this odd if there are only non-editable visible nodes?
5285 if (HTMLEditUtils::IsEmptyOneHardLine(
5286 arrayOfContents, BlockInlineCheck::UseHTMLDefaultStyle)) {
5287 const EditorDOMPoint pointToInsertDivElement =
5288 pointToPutCaret.IsSet()
5289 ? std::move(pointToPutCaret)
5290 : aRanges.GetFirstRangeStartPoint<EditorDOMPoint>();
5291 if (NS_WARN_IF(!pointToInsertDivElement.IsSet())) {
5292 return NS_ERROR_FAILURE;
5295 // make sure we can put a block here
5296 Result<CreateElementResult, nsresult> createNewDivElementResult =
5297 InsertElementWithSplittingAncestorsWithTransaction(
5298 *nsGkAtoms::div, pointToInsertDivElement,
5299 BRElementNextToSplitPoint::Keep, aEditingHost);
5300 if (MOZ_UNLIKELY(createNewDivElementResult.isErr())) {
5301 NS_WARNING(
5302 "HTMLEditor::InsertElementWithSplittingAncestorsWithTransaction("
5303 "nsGkAtoms::div) failed");
5304 return createNewDivElementResult.unwrapErr();
5306 CreateElementResult unwrappedCreateNewDivElementResult =
5307 createNewDivElementResult.unwrap();
5308 // We'll collapse ranges below, so we don't need to touch the ranges here.
5309 unwrappedCreateNewDivElementResult.IgnoreCaretPointSuggestion();
5310 const RefPtr<Element> newDivElement =
5311 unwrappedCreateNewDivElementResult.UnwrapNewNode();
5312 MOZ_ASSERT(newDivElement);
5313 const Result<EditorDOMPoint, nsresult> pointToPutCaretOrError =
5314 ChangeMarginStart(*newDivElement, ChangeMargin::Increase, aEditingHost);
5315 if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) {
5316 if (NS_WARN_IF(pointToPutCaretOrError.inspectErr() ==
5317 NS_ERROR_EDITOR_DESTROYED)) {
5318 return NS_ERROR_EDITOR_DESTROYED;
5320 NS_WARNING(
5321 "HTMLEditor::ChangeMarginStart(ChangeMargin::Increase) failed, but "
5322 "ignored");
5324 // delete anything that was in the list of nodes
5325 // XXX We don't need to remove the nodes from the array for performance.
5326 for (const OwningNonNull<nsIContent>& content : arrayOfContents) {
5327 // MOZ_KnownLive(content) due to bug 1622253
5328 nsresult rv = DeleteNodeWithTransaction(MOZ_KnownLive(content));
5329 if (NS_FAILED(rv)) {
5330 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
5331 return rv;
5334 aRanges.ClearSavedRanges();
5335 nsresult rv = aRanges.Collapse(EditorDOMPoint(newDivElement, 0u));
5336 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "AutoRangeArray::Collapse() failed");
5337 return rv;
5340 RefPtr<Element> latestNewBlockElement;
5341 auto RestoreSavedRangesAndCollapseInLatestBlockElementIfOutside =
5342 [&]() -> nsresult {
5343 MOZ_ASSERT(aRanges.HasSavedRanges());
5344 aRanges.RestoreFromSavedRanges();
5346 if (!latestNewBlockElement || !aRanges.IsCollapsed() ||
5347 aRanges.Ranges().IsEmpty()) {
5348 return NS_OK;
5351 const auto firstRangeStartRawPoint =
5352 aRanges.GetFirstRangeStartPoint<EditorRawDOMPoint>();
5353 if (MOZ_UNLIKELY(!firstRangeStartRawPoint.IsSet())) {
5354 return NS_OK;
5356 Result<EditorRawDOMPoint, nsresult> pointInNewBlockElementOrError =
5357 HTMLEditUtils::ComputePointToPutCaretInElementIfOutside<
5358 EditorRawDOMPoint>(*latestNewBlockElement, firstRangeStartRawPoint);
5359 if (MOZ_UNLIKELY(pointInNewBlockElementOrError.isErr())) {
5360 NS_WARNING(
5361 "HTMLEditUtils::ComputePointToPutCaretInElementIfOutside() failed, "
5362 "but ignored");
5363 return NS_OK;
5365 if (!pointInNewBlockElementOrError.inspect().IsSet()) {
5366 return NS_OK;
5368 return aRanges.Collapse(pointInNewBlockElementOrError.unwrap());
5371 // Ok, now go through all the nodes and put them into sub-list element
5372 // elements and new <div> elements which have start margin.
5373 RefPtr<Element> subListElement, divElement;
5374 for (OwningNonNull<nsIContent>& content : arrayOfContents) {
5375 // Here's where we actually figure out what to do.
5376 EditorDOMPoint atContent(content);
5377 if (NS_WARN_IF(!atContent.IsSet())) {
5378 continue;
5381 // Ignore all non-editable nodes. Leave them be.
5382 // XXX We ignore non-editable nodes here, but not so in the above block.
5383 if (!EditorUtils::IsEditableContent(content, EditorType::HTML)) {
5384 continue;
5387 if (HTMLEditUtils::IsAnyListElement(atContent.GetContainer())) {
5388 const RefPtr<Element> oldSubListElement = subListElement;
5389 // MOZ_KnownLive because 'arrayOfContents' is guaranteed to
5390 // keep it alive.
5391 Result<EditorDOMPoint, nsresult> pointToPutCaretOrError =
5392 IndentListChildWithTransaction(&subListElement, atContent,
5393 MOZ_KnownLive(content), aEditingHost);
5394 if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) {
5395 NS_WARNING("HTMLEditor::IndentListChildWithTransaction() failed");
5396 return pointToPutCaretOrError.unwrapErr();
5398 if (subListElement != oldSubListElement) {
5399 // New list element is created, so we should put caret into the new list
5400 // element.
5401 latestNewBlockElement = subListElement;
5403 if (pointToPutCaretOrError.inspect().IsSet()) {
5404 pointToPutCaret = pointToPutCaretOrError.unwrap();
5406 continue;
5409 // Not a list item.
5411 if (HTMLEditUtils::IsBlockElement(content,
5412 BlockInlineCheck::UseHTMLDefaultStyle)) {
5413 Result<EditorDOMPoint, nsresult> pointToPutCaretOrError =
5414 ChangeMarginStart(MOZ_KnownLive(*content->AsElement()),
5415 ChangeMargin::Increase, aEditingHost);
5416 if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) {
5417 if (MOZ_UNLIKELY(pointToPutCaretOrError.inspectErr() ==
5418 NS_ERROR_EDITOR_DESTROYED)) {
5419 NS_WARNING(
5420 "HTMLEditor::ChangeMarginStart(ChangeMargin::Increase) failed");
5421 return NS_ERROR_EDITOR_DESTROYED;
5423 NS_WARNING(
5424 "HTMLEditor::ChangeMarginStart(ChangeMargin::Increase) failed, but "
5425 "ignored");
5426 } else if (pointToPutCaretOrError.inspect().IsSet()) {
5427 pointToPutCaret = pointToPutCaretOrError.unwrap();
5429 divElement = nullptr;
5430 continue;
5433 if (!divElement) {
5434 // First, check that our element can contain a div.
5435 if (!HTMLEditUtils::CanNodeContain(*atContent.GetContainer(),
5436 *nsGkAtoms::div)) {
5437 // XXX This is odd, why do we stop indenting remaining content nodes?
5438 // Perhaps, `continue` is better.
5439 nsresult rv =
5440 RestoreSavedRangesAndCollapseInLatestBlockElementIfOutside();
5441 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
5442 "RestoreSavedRangesAndCollapseInLatestBlockElement"
5443 "IfOutside() failed");
5444 return rv;
5447 Result<CreateElementResult, nsresult> createNewDivElementResult =
5448 InsertElementWithSplittingAncestorsWithTransaction(
5449 *nsGkAtoms::div, atContent, BRElementNextToSplitPoint::Keep,
5450 aEditingHost);
5451 if (MOZ_UNLIKELY(createNewDivElementResult.isErr())) {
5452 NS_WARNING(
5453 "HTMLEditor::InsertElementWithSplittingAncestorsWithTransaction("
5454 "nsGkAtoms::div) failed");
5455 return createNewDivElementResult.unwrapErr();
5457 CreateElementResult unwrappedCreateNewDivElementResult =
5458 createNewDivElementResult.unwrap();
5459 pointToPutCaret = unwrappedCreateNewDivElementResult.UnwrapCaretPoint();
5461 MOZ_ASSERT(unwrappedCreateNewDivElementResult.GetNewNode());
5462 divElement = unwrappedCreateNewDivElementResult.UnwrapNewNode();
5463 Result<EditorDOMPoint, nsresult> pointToPutCaretOrError =
5464 ChangeMarginStart(*divElement, ChangeMargin::Increase, aEditingHost);
5465 if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) {
5466 if (MOZ_UNLIKELY(pointToPutCaretOrError.inspectErr() ==
5467 NS_ERROR_EDITOR_DESTROYED)) {
5468 NS_WARNING(
5469 "HTMLEditor::ChangeMarginStart(ChangeMargin::Increase) failed");
5470 return NS_ERROR_EDITOR_DESTROYED;
5472 NS_WARNING(
5473 "HTMLEditor::ChangeMarginStart(ChangeMargin::Increase) failed, but "
5474 "ignored");
5475 } else if (AllowsTransactionsToChangeSelection() &&
5476 pointToPutCaretOrError.inspect().IsSet()) {
5477 pointToPutCaret = pointToPutCaretOrError.unwrap();
5480 latestNewBlockElement = divElement;
5483 // Move the content into the <div> which has start margin.
5484 // MOZ_KnownLive because 'arrayOfContents' is guaranteed to
5485 // keep it alive.
5486 Result<MoveNodeResult, nsresult> moveNodeResult =
5487 MoveNodeToEndWithTransaction(MOZ_KnownLive(content), *divElement);
5488 if (MOZ_UNLIKELY(moveNodeResult.isErr())) {
5489 NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed");
5490 return moveNodeResult.unwrapErr();
5492 MoveNodeResult unwrappedMoveNodeResult = moveNodeResult.unwrap();
5493 if (unwrappedMoveNodeResult.HasCaretPointSuggestion()) {
5494 pointToPutCaret = unwrappedMoveNodeResult.UnwrapCaretPoint();
5498 nsresult rv = RestoreSavedRangesAndCollapseInLatestBlockElementIfOutside();
5499 NS_WARNING_ASSERTION(
5500 NS_SUCCEEDED(rv),
5501 "RestoreSavedRangesAndCollapseInLatestBlockElementIfOutside() failed");
5502 return rv;
5505 nsresult HTMLEditor::HandleHTMLIndentAroundRanges(AutoRangeArray& aRanges,
5506 const Element& aEditingHost) {
5507 MOZ_ASSERT(IsEditActionDataAvailable());
5508 MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable());
5509 MOZ_ASSERT(!aRanges.Ranges().IsEmpty());
5510 MOZ_ASSERT(aRanges.IsInContent());
5512 // XXX Why do we do this only when there is only one range?
5513 if (!aRanges.IsCollapsed() && aRanges.Ranges().Length() == 1u) {
5514 Result<EditorRawDOMRange, nsresult> extendedRange =
5515 GetRangeExtendedToHardLineEdgesForBlockEditAction(
5516 aRanges.FirstRangeRef(), aEditingHost);
5517 if (MOZ_UNLIKELY(extendedRange.isErr())) {
5518 NS_WARNING(
5519 "HTMLEditor::GetRangeExtendedToHardLineEdgesForBlockEditAction() "
5520 "failed");
5521 return extendedRange.unwrapErr();
5523 // Note that end point may be prior to start point. So, we cannot use
5524 // SetStartAndEnd() here.
5525 nsresult rv = aRanges.SetBaseAndExtent(extendedRange.inspect().StartRef(),
5526 extendedRange.inspect().EndRef());
5527 if (NS_FAILED(rv)) {
5528 NS_WARNING("AutoRangeArray::SetBaseAndExtent() failed");
5529 return rv;
5533 if (NS_WARN_IF(!aRanges.SaveAndTrackRanges(*this))) {
5534 return NS_ERROR_FAILURE;
5537 EditorDOMPoint pointToPutCaret;
5539 // convert the selection ranges into "promoted" selection ranges:
5540 // this basically just expands the range to include the immediate
5541 // block parent, and then further expands to include any ancestors
5542 // whose children are all in the range
5544 // use these ranges to construct a list of nodes to act on.
5545 AutoTArray<OwningNonNull<nsIContent>, 64> arrayOfContents;
5547 AutoRangeArray extendedRanges(aRanges);
5548 extendedRanges.ExtendRangesToWrapLines(
5549 EditSubAction::eIndent, BlockInlineCheck::UseHTMLDefaultStyle,
5550 aEditingHost);
5551 Result<EditorDOMPoint, nsresult> splitResult =
5552 extendedRanges
5553 .SplitTextAtEndBoundariesAndInlineAncestorsAtBothBoundaries(
5554 *this, BlockInlineCheck::UseHTMLDefaultStyle, aEditingHost);
5555 if (MOZ_UNLIKELY(splitResult.isErr())) {
5556 NS_WARNING(
5557 "AutoRangeArray::"
5558 "SplitTextAtEndBoundariesAndInlineAncestorsAtBothBoundaries() "
5559 "failed");
5560 return splitResult.unwrapErr();
5562 if (splitResult.inspect().IsSet()) {
5563 pointToPutCaret = splitResult.unwrap();
5565 nsresult rv = extendedRanges.CollectEditTargetNodes(
5566 *this, arrayOfContents, EditSubAction::eIndent,
5567 AutoRangeArray::CollectNonEditableNodes::Yes);
5568 if (NS_FAILED(rv)) {
5569 NS_WARNING(
5570 "AutoRangeArray::CollectEditTargetNodes(EditSubAction::eIndent, "
5571 "CollectNonEditableNodes::Yes) failed");
5572 return rv;
5576 // FIXME: Split ancestors when we consider to indent the range.
5577 Result<EditorDOMPoint, nsresult> splitAtBRElementsResult =
5578 MaybeSplitElementsAtEveryBRElement(arrayOfContents,
5579 EditSubAction::eIndent);
5580 if (MOZ_UNLIKELY(splitAtBRElementsResult.isErr())) {
5581 NS_WARNING(
5582 "HTMLEditor::MaybeSplitElementsAtEveryBRElement(EditSubAction::eIndent)"
5583 " failed");
5584 return splitAtBRElementsResult.inspectErr();
5586 if (splitAtBRElementsResult.inspect().IsSet()) {
5587 pointToPutCaret = splitAtBRElementsResult.unwrap();
5590 // If there is no visible and editable nodes in the edit targets, make an
5591 // empty block.
5592 // XXX Isn't this odd if there are only non-editable visible nodes?
5593 if (HTMLEditUtils::IsEmptyOneHardLine(
5594 arrayOfContents, BlockInlineCheck::UseHTMLDefaultStyle)) {
5595 const EditorDOMPoint pointToInsertBlockquoteElement =
5596 pointToPutCaret.IsSet()
5597 ? std::move(pointToPutCaret)
5598 : EditorBase::GetFirstSelectionStartPoint<EditorDOMPoint>();
5599 if (NS_WARN_IF(!pointToInsertBlockquoteElement.IsSet())) {
5600 return NS_ERROR_FAILURE;
5603 // If there is no element which can have <blockquote>, abort.
5604 if (NS_WARN_IF(!HTMLEditUtils::GetInsertionPointInInclusiveAncestor(
5605 *nsGkAtoms::blockquote, pointToInsertBlockquoteElement,
5606 &aEditingHost)
5607 .IsSet())) {
5608 return NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE;
5611 // Make sure we can put a block here.
5612 // XXX Unfortunately, this calls
5613 // MaybeSplitAncestorsForInsertWithTransaction() then,
5614 // HTMLEditUtils::GetInsertionPointInInclusiveAncestor() is called again.
5615 Result<CreateElementResult, nsresult> createNewBlockquoteElementResult =
5616 InsertElementWithSplittingAncestorsWithTransaction(
5617 *nsGkAtoms::blockquote, pointToInsertBlockquoteElement,
5618 BRElementNextToSplitPoint::Keep, aEditingHost);
5619 if (MOZ_UNLIKELY(createNewBlockquoteElementResult.isErr())) {
5620 NS_WARNING(
5621 "HTMLEditor::InsertElementWithSplittingAncestorsWithTransaction("
5622 "nsGkAtoms::blockquote) failed");
5623 return createNewBlockquoteElementResult.unwrapErr();
5625 CreateElementResult unwrappedCreateNewBlockquoteElementResult =
5626 createNewBlockquoteElementResult.unwrap();
5627 unwrappedCreateNewBlockquoteElementResult.IgnoreCaretPointSuggestion();
5628 RefPtr<Element> newBlockquoteElement =
5629 unwrappedCreateNewBlockquoteElementResult.UnwrapNewNode();
5630 MOZ_ASSERT(newBlockquoteElement);
5631 // delete anything that was in the list of nodes
5632 // XXX We don't need to remove the nodes from the array for performance.
5633 for (const OwningNonNull<nsIContent>& content : arrayOfContents) {
5634 // MOZ_KnownLive because 'arrayOfContents' is guaranteed to
5635 // keep it alive.
5636 nsresult rv = DeleteNodeWithTransaction(MOZ_KnownLive(*content));
5637 if (NS_FAILED(rv)) {
5638 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
5639 return rv;
5642 aRanges.ClearSavedRanges();
5643 nsresult rv = aRanges.Collapse(EditorRawDOMPoint(newBlockquoteElement, 0u));
5644 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
5645 "EditorBase::CollapseSelectionToStartOf() failed");
5646 return rv;
5649 RefPtr<Element> latestNewBlockElement;
5650 auto RestoreSavedRangesAndCollapseInLatestBlockElementIfOutside =
5651 [&]() -> nsresult {
5652 MOZ_ASSERT(aRanges.HasSavedRanges());
5653 aRanges.RestoreFromSavedRanges();
5655 if (!latestNewBlockElement || !aRanges.IsCollapsed() ||
5656 aRanges.Ranges().IsEmpty()) {
5657 return NS_OK;
5660 const auto firstRangeStartRawPoint =
5661 aRanges.GetFirstRangeStartPoint<EditorRawDOMPoint>();
5662 if (MOZ_UNLIKELY(!firstRangeStartRawPoint.IsSet())) {
5663 return NS_OK;
5665 Result<EditorRawDOMPoint, nsresult> pointInNewBlockElementOrError =
5666 HTMLEditUtils::ComputePointToPutCaretInElementIfOutside<
5667 EditorRawDOMPoint>(*latestNewBlockElement, firstRangeStartRawPoint);
5668 if (MOZ_UNLIKELY(pointInNewBlockElementOrError.isErr())) {
5669 NS_WARNING(
5670 "HTMLEditUtils::ComputePointToPutCaretInElementIfOutside() failed, "
5671 "but ignored");
5672 return NS_OK;
5674 if (!pointInNewBlockElementOrError.inspect().IsSet()) {
5675 return NS_OK;
5677 return aRanges.Collapse(pointInNewBlockElementOrError.unwrap());
5680 // Ok, now go through all the nodes and put them in a blockquote,
5681 // or whatever is appropriate. Wohoo!
5682 RefPtr<Element> subListElement, blockquoteElement, indentedListItemElement;
5683 for (OwningNonNull<nsIContent>& content : arrayOfContents) {
5684 // Here's where we actually figure out what to do.
5685 EditorDOMPoint atContent(content);
5686 if (NS_WARN_IF(!atContent.IsSet())) {
5687 continue;
5690 // Ignore all non-editable nodes. Leave them be.
5691 // XXX We ignore non-editable nodes here, but not so in the above block.
5692 if (!EditorUtils::IsEditableContent(content, EditorType::HTML) ||
5693 !HTMLEditUtils::IsRemovableNode(content)) {
5694 continue;
5697 // If the content has been moved to different place, ignore it.
5698 if (MOZ_UNLIKELY(!content->IsInclusiveDescendantOf(&aEditingHost))) {
5699 continue;
5702 if (HTMLEditUtils::IsAnyListElement(atContent.GetContainer())) {
5703 const RefPtr<Element> oldSubListElement = subListElement;
5704 // MOZ_KnownLive because 'arrayOfContents' is guaranteed to
5705 // keep it alive.
5706 Result<EditorDOMPoint, nsresult> pointToPutCaretOrError =
5707 IndentListChildWithTransaction(&subListElement, atContent,
5708 MOZ_KnownLive(content), aEditingHost);
5709 if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) {
5710 NS_WARNING("HTMLEditor::IndentListChildWithTransaction() failed");
5711 return pointToPutCaretOrError.unwrapErr();
5713 if (oldSubListElement != subListElement) {
5714 // New list element is created, so we should put caret into the new list
5715 // element.
5716 latestNewBlockElement = subListElement;
5718 if (pointToPutCaretOrError.inspect().IsSet()) {
5719 pointToPutCaret = pointToPutCaretOrError.unwrap();
5721 blockquoteElement = nullptr;
5722 continue;
5725 // Not a list item, use blockquote?
5727 // if we are inside a list item, we don't want to blockquote, we want
5728 // to sublist the list item. We may have several nodes listed in the
5729 // array of nodes to act on, that are in the same list item. Since
5730 // we only want to indent that li once, we must keep track of the most
5731 // recent indented list item, and not indent it if we find another node
5732 // to act on that is still inside the same li.
5733 if (RefPtr<Element> listItem =
5734 HTMLEditUtils::GetClosestAncestorListItemElement(content,
5735 &aEditingHost)) {
5736 if (indentedListItemElement == listItem) {
5737 // already indented this list item
5738 continue;
5740 // check to see if subListElement is still appropriate. Which it is if
5741 // content is still right after it in the same list.
5742 nsIContent* previousEditableSibling =
5743 subListElement
5744 ? HTMLEditUtils::GetPreviousSibling(
5745 *listItem, {WalkTreeOption::IgnoreNonEditableNode})
5746 : nullptr;
5747 if (!subListElement || (previousEditableSibling &&
5748 previousEditableSibling != subListElement)) {
5749 EditorDOMPoint atListItem(listItem);
5750 if (NS_WARN_IF(!listItem)) {
5751 return NS_ERROR_FAILURE;
5753 nsAtom* containerName =
5754 atListItem.GetContainer()->NodeInfo()->NameAtom();
5755 // Create a new nested list of correct type.
5756 Result<CreateElementResult, nsresult> createNewListElementResult =
5757 InsertElementWithSplittingAncestorsWithTransaction(
5758 MOZ_KnownLive(*containerName), atListItem,
5759 BRElementNextToSplitPoint::Keep, aEditingHost);
5760 if (MOZ_UNLIKELY(createNewListElementResult.isErr())) {
5761 NS_WARNING(nsPrintfCString("HTMLEditor::"
5762 "InsertElementWithSplittingAncestorsWithTr"
5763 "ansaction(%s) failed",
5764 nsAtomCString(containerName).get())
5765 .get());
5766 return createNewListElementResult.unwrapErr();
5768 CreateElementResult unwrappedCreateNewListElementResult =
5769 createNewListElementResult.unwrap();
5770 if (unwrappedCreateNewListElementResult.HasCaretPointSuggestion()) {
5771 pointToPutCaret =
5772 unwrappedCreateNewListElementResult.UnwrapCaretPoint();
5774 MOZ_ASSERT(unwrappedCreateNewListElementResult.GetNewNode());
5775 subListElement = unwrappedCreateNewListElementResult.UnwrapNewNode();
5778 Result<MoveNodeResult, nsresult> moveListItemElementResult =
5779 MoveNodeToEndWithTransaction(*listItem, *subListElement);
5780 if (MOZ_UNLIKELY(moveListItemElementResult.isErr())) {
5781 NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed");
5782 return moveListItemElementResult.unwrapErr();
5784 MoveNodeResult unwrappedMoveListItemElementResult =
5785 moveListItemElementResult.unwrap();
5786 if (unwrappedMoveListItemElementResult.HasCaretPointSuggestion()) {
5787 pointToPutCaret = unwrappedMoveListItemElementResult.UnwrapCaretPoint();
5790 // Remember the list item element which we indented now for ignoring its
5791 // children to avoid using <blockquote> in it.
5792 indentedListItemElement = std::move(listItem);
5794 continue;
5797 // need to make a blockquote to put things in if we haven't already,
5798 // or if this node doesn't go in blockquote we used earlier.
5799 // One reason it might not go in prio blockquote is if we are now
5800 // in a different table cell.
5801 if (blockquoteElement &&
5802 HTMLEditUtils::GetInclusiveAncestorAnyTableElement(
5803 *blockquoteElement) !=
5804 HTMLEditUtils::GetInclusiveAncestorAnyTableElement(content)) {
5805 blockquoteElement = nullptr;
5808 if (!blockquoteElement) {
5809 // First, check that our element can contain a blockquote.
5810 if (!HTMLEditUtils::CanNodeContain(*atContent.GetContainer(),
5811 *nsGkAtoms::blockquote)) {
5812 // XXX This is odd, why do we stop indenting remaining content nodes?
5813 // Perhaps, `continue` is better.
5814 nsresult rv =
5815 RestoreSavedRangesAndCollapseInLatestBlockElementIfOutside();
5816 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
5817 "RestoreSavedRangesAndCollapseInLatestBlockElement"
5818 "IfOutside() failed");
5819 return rv;
5822 Result<CreateElementResult, nsresult> createNewBlockquoteElementResult =
5823 InsertElementWithSplittingAncestorsWithTransaction(
5824 *nsGkAtoms::blockquote, atContent,
5825 BRElementNextToSplitPoint::Keep, aEditingHost);
5826 if (MOZ_UNLIKELY(createNewBlockquoteElementResult.isErr())) {
5827 NS_WARNING(
5828 "HTMLEditor::InsertElementWithSplittingAncestorsWithTransaction("
5829 "nsGkAtoms::blockquote) failed");
5830 return createNewBlockquoteElementResult.unwrapErr();
5832 CreateElementResult unwrappedCreateNewBlockquoteElementResult =
5833 createNewBlockquoteElementResult.unwrap();
5834 if (unwrappedCreateNewBlockquoteElementResult.HasCaretPointSuggestion()) {
5835 pointToPutCaret =
5836 unwrappedCreateNewBlockquoteElementResult.UnwrapCaretPoint();
5839 MOZ_ASSERT(unwrappedCreateNewBlockquoteElementResult.GetNewNode());
5840 blockquoteElement =
5841 unwrappedCreateNewBlockquoteElementResult.UnwrapNewNode();
5842 latestNewBlockElement = blockquoteElement;
5845 // tuck the node into the end of the active blockquote
5846 // MOZ_KnownLive because 'arrayOfContents' is guaranteed to
5847 // keep it alive.
5848 Result<MoveNodeResult, nsresult> moveNodeResult =
5849 MoveNodeToEndWithTransaction(MOZ_KnownLive(content),
5850 *blockquoteElement);
5851 if (MOZ_UNLIKELY(moveNodeResult.isErr())) {
5852 NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed");
5853 return moveNodeResult.unwrapErr();
5855 MoveNodeResult unwrappedMoveNodeResult = moveNodeResult.unwrap();
5856 if (unwrappedMoveNodeResult.HasCaretPointSuggestion()) {
5857 pointToPutCaret = unwrappedMoveNodeResult.UnwrapCaretPoint();
5859 subListElement = nullptr;
5862 nsresult rv = RestoreSavedRangesAndCollapseInLatestBlockElementIfOutside();
5863 NS_WARNING_ASSERTION(
5864 NS_SUCCEEDED(rv),
5865 "RestoreSavedRangesAndCollapseInLatestBlockElementIfOutside() failed");
5866 return rv;
5869 Result<EditActionResult, nsresult> HTMLEditor::OutdentAsSubAction(
5870 const Element& aEditingHost) {
5871 MOZ_ASSERT(IsEditActionDataAvailable());
5873 AutoPlaceholderBatch treatAsOneTransaction(
5874 *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
5875 IgnoredErrorResult ignoredError;
5876 AutoEditSubActionNotifier startToHandleEditSubAction(
5877 *this, EditSubAction::eOutdent, nsIEditor::eNext, ignoredError);
5878 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
5879 return Err(ignoredError.StealNSResult());
5881 NS_WARNING_ASSERTION(
5882 !ignoredError.Failed(),
5883 "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
5886 Result<EditActionResult, nsresult> result = CanHandleHTMLEditSubAction();
5887 if (MOZ_UNLIKELY(result.isErr())) {
5888 NS_WARNING("HTMLEditor::CanHandleHTMLEditSubAction() failed");
5889 return result;
5891 if (result.inspect().Canceled()) {
5892 return result;
5896 if (MOZ_UNLIKELY(IsSelectionRangeContainerNotContent())) {
5897 NS_WARNING("Some selection containers are not content node, but ignored");
5898 return EditActionResult::IgnoredResult();
5901 Result<EditActionResult, nsresult> result =
5902 HandleOutdentAtSelection(aEditingHost);
5903 if (MOZ_UNLIKELY(result.isErr())) {
5904 NS_WARNING("HTMLEditor::HandleOutdentAtSelection() failed");
5905 return result;
5907 if (result.inspect().Canceled()) {
5908 return result;
5911 if (MOZ_UNLIKELY(IsSelectionRangeContainerNotContent())) {
5912 NS_WARNING("Mutation event listener might have changed the selection");
5913 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
5916 nsresult rv = MaybeInsertPaddingBRElementForEmptyLastLineAtSelection();
5917 if (NS_FAILED(rv)) {
5918 NS_WARNING(
5919 "HTMLEditor::MaybeInsertPaddingBRElementForEmptyLastLineAtSelection() "
5920 "failed");
5921 return Err(rv);
5923 return result;
5926 Result<EditActionResult, nsresult> HTMLEditor::HandleOutdentAtSelection(
5927 const Element& aEditingHost) {
5928 MOZ_ASSERT(IsEditActionDataAvailable());
5929 MOZ_ASSERT(!IsSelectionRangeContainerNotContent());
5931 // XXX Why do we do this only when there is only one selection range?
5932 if (!SelectionRef().IsCollapsed() && SelectionRef().RangeCount() == 1u) {
5933 Result<EditorRawDOMRange, nsresult> extendedRange =
5934 GetRangeExtendedToHardLineEdgesForBlockEditAction(
5935 SelectionRef().GetRangeAt(0u), aEditingHost);
5936 if (MOZ_UNLIKELY(extendedRange.isErr())) {
5937 NS_WARNING(
5938 "HTMLEditor::GetRangeExtendedToHardLineEdgesForBlockEditAction() "
5939 "failed");
5940 return extendedRange.propagateErr();
5942 // Note that end point may be prior to start point. So, we
5943 // cannot use Selection::SetStartAndEndInLimit() here.
5944 IgnoredErrorResult error;
5945 SelectionRef().SetBaseAndExtentInLimiter(
5946 extendedRange.inspect().StartRef().ToRawRangeBoundary(),
5947 extendedRange.inspect().EndRef().ToRawRangeBoundary(), error);
5948 if (NS_WARN_IF(Destroyed())) {
5949 return Err(NS_ERROR_EDITOR_DESTROYED);
5951 if (MOZ_UNLIKELY(error.Failed())) {
5952 NS_WARNING("Selection::SetBaseAndExtentInLimiter() failed");
5953 return Err(error.StealNSResult());
5957 // HandleOutdentAtSelectionInternal() creates AutoSelectionRestorer.
5958 // Therefore, even if it returns NS_OK, the editor might have been destroyed
5959 // at restoring Selection.
5960 Result<SplitRangeOffFromNodeResult, nsresult> outdentResult =
5961 HandleOutdentAtSelectionInternal(aEditingHost);
5962 MOZ_ASSERT_IF(outdentResult.isOk(),
5963 !outdentResult.inspect().HasCaretPointSuggestion());
5964 if (NS_WARN_IF(Destroyed())) {
5965 return Err(NS_ERROR_EDITOR_DESTROYED);
5967 if (MOZ_UNLIKELY(outdentResult.isErr())) {
5968 NS_WARNING("HTMLEditor::HandleOutdentAtSelectionInternal() failed");
5969 return outdentResult.propagateErr();
5971 SplitRangeOffFromNodeResult unwrappedOutdentResult = outdentResult.unwrap();
5973 // Make sure selection didn't stick to last piece of content in old bq (only
5974 // a problem for collapsed selections)
5975 if (!unwrappedOutdentResult.GetLeftContent() &&
5976 !unwrappedOutdentResult.GetRightContent()) {
5977 return EditActionResult::HandledResult();
5980 if (!SelectionRef().IsCollapsed()) {
5981 return EditActionResult::HandledResult();
5984 // Push selection past end of left element of last split indented element.
5985 if (unwrappedOutdentResult.GetLeftContent()) {
5986 const nsRange* firstRange = SelectionRef().GetRangeAt(0);
5987 if (NS_WARN_IF(!firstRange)) {
5988 return EditActionResult::HandledResult();
5990 const RangeBoundary& atStartOfSelection = firstRange->StartRef();
5991 if (NS_WARN_IF(!atStartOfSelection.IsSet())) {
5992 return Err(NS_ERROR_FAILURE);
5994 if (atStartOfSelection.Container() ==
5995 unwrappedOutdentResult.GetLeftContent() ||
5996 EditorUtils::IsDescendantOf(*atStartOfSelection.Container(),
5997 *unwrappedOutdentResult.GetLeftContent())) {
5998 // Selection is inside the left node - push it past it.
5999 EditorRawDOMPoint afterRememberedLeftBQ(
6000 EditorRawDOMPoint::After(*unwrappedOutdentResult.GetLeftContent()));
6001 NS_WARNING_ASSERTION(
6002 afterRememberedLeftBQ.IsSet(),
6003 "Failed to set after remembered left blockquote element");
6004 nsresult rv = CollapseSelectionTo(afterRememberedLeftBQ);
6005 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
6006 return Err(NS_ERROR_EDITOR_DESTROYED);
6008 NS_WARNING_ASSERTION(
6009 NS_SUCCEEDED(rv),
6010 "EditorBase::CollapseSelectionTo() failed, but ignored");
6013 // And pull selection before beginning of right element of last split
6014 // indented element.
6015 if (unwrappedOutdentResult.GetRightContent()) {
6016 const nsRange* firstRange = SelectionRef().GetRangeAt(0);
6017 if (NS_WARN_IF(!firstRange)) {
6018 return EditActionResult::HandledResult();
6020 const RangeBoundary& atStartOfSelection = firstRange->StartRef();
6021 if (NS_WARN_IF(!atStartOfSelection.IsSet())) {
6022 return Err(NS_ERROR_FAILURE);
6024 if (atStartOfSelection.Container() ==
6025 unwrappedOutdentResult.GetRightContent() ||
6026 EditorUtils::IsDescendantOf(
6027 *atStartOfSelection.Container(),
6028 *unwrappedOutdentResult.GetRightContent())) {
6029 // Selection is inside the right element - push it before it.
6030 EditorRawDOMPoint atRememberedRightBQ(
6031 unwrappedOutdentResult.GetRightContent());
6032 nsresult rv = CollapseSelectionTo(atRememberedRightBQ);
6033 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
6034 return Err(NS_ERROR_EDITOR_DESTROYED);
6036 NS_WARNING_ASSERTION(
6037 NS_SUCCEEDED(rv),
6038 "EditorBase::CollapseSelectionTo() failed, but ignored");
6041 return EditActionResult::HandledResult();
6044 Result<SplitRangeOffFromNodeResult, nsresult>
6045 HTMLEditor::HandleOutdentAtSelectionInternal(const Element& aEditingHost) {
6046 MOZ_ASSERT(IsEditActionDataAvailable());
6048 AutoSelectionRestorer restoreSelectionLater(this);
6050 bool useCSS = IsCSSEnabled();
6052 // Convert the selection ranges into "promoted" selection ranges: this
6053 // basically just expands the range to include the immediate block parent,
6054 // and then further expands to include any ancestors whose children are all
6055 // in the range
6056 AutoTArray<OwningNonNull<nsIContent>, 64> arrayOfContents;
6058 AutoRangeArray extendedSelectionRanges(SelectionRef());
6059 extendedSelectionRanges.ExtendRangesToWrapLines(
6060 EditSubAction::eOutdent, BlockInlineCheck::UseHTMLDefaultStyle,
6061 aEditingHost);
6062 nsresult rv = extendedSelectionRanges.CollectEditTargetNodes(
6063 *this, arrayOfContents, EditSubAction::eOutdent,
6064 AutoRangeArray::CollectNonEditableNodes::Yes);
6065 if (NS_FAILED(rv)) {
6066 NS_WARNING(
6067 "AutoRangeArray::CollectEditTargetNodes(EditSubAction::eOutdent, "
6068 "CollectNonEditableNodes::Yes) failed");
6069 return Err(rv);
6071 Result<EditorDOMPoint, nsresult> splitAtBRElementsResult =
6072 MaybeSplitElementsAtEveryBRElement(arrayOfContents,
6073 EditSubAction::eOutdent);
6074 if (MOZ_UNLIKELY(splitAtBRElementsResult.isErr())) {
6075 NS_WARNING(
6076 "HTMLEditor::MaybeSplitElementsAtEveryBRElement(EditSubAction::"
6077 "eOutdent) failed");
6078 return splitAtBRElementsResult.propagateErr();
6080 if (AllowsTransactionsToChangeSelection() &&
6081 splitAtBRElementsResult.inspect().IsSet()) {
6082 nsresult rv = CollapseSelectionTo(splitAtBRElementsResult.inspect());
6083 if (NS_FAILED(rv)) {
6084 NS_WARNING("EditorBase::CollapseSelectionTo() failed");
6085 return Err(rv);
6090 nsCOMPtr<nsIContent> leftContentOfLastOutdented;
6091 nsCOMPtr<nsIContent> middleContentOfLastOutdented;
6092 nsCOMPtr<nsIContent> rightContentOfLastOutdented;
6093 RefPtr<Element> indentedParentElement;
6094 nsCOMPtr<nsIContent> firstContentToBeOutdented, lastContentToBeOutdented;
6095 BlockIndentedWith indentedParentIndentedWith = BlockIndentedWith::HTML;
6096 for (OwningNonNull<nsIContent>& content : arrayOfContents) {
6097 // Here's where we actually figure out what to do
6098 EditorDOMPoint atContent(content);
6099 if (!atContent.IsSet()) {
6100 continue;
6103 // If it's a `<blockquote>`, remove it to outdent its children.
6104 if (content->IsHTMLElement(nsGkAtoms::blockquote)) {
6105 // If we've already found an ancestor block element indented, we need to
6106 // split it and remove the block element first.
6107 if (indentedParentElement) {
6108 NS_WARNING_ASSERTION(indentedParentElement == content,
6109 "Indented parent element is not the <blockquote>");
6110 Result<SplitRangeOffFromNodeResult, nsresult> outdentResult =
6111 OutdentPartOfBlock(*indentedParentElement,
6112 *firstContentToBeOutdented,
6113 *lastContentToBeOutdented,
6114 indentedParentIndentedWith, aEditingHost);
6115 if (MOZ_UNLIKELY(outdentResult.isErr())) {
6116 NS_WARNING("HTMLEditor::OutdentPartOfBlock() failed");
6117 return outdentResult;
6119 SplitRangeOffFromNodeResult unwrappedOutdentResult =
6120 outdentResult.unwrap();
6121 unwrappedOutdentResult.IgnoreCaretPointSuggestion();
6122 leftContentOfLastOutdented = unwrappedOutdentResult.UnwrapLeftContent();
6123 middleContentOfLastOutdented =
6124 unwrappedOutdentResult.UnwrapMiddleContent();
6125 rightContentOfLastOutdented =
6126 unwrappedOutdentResult.UnwrapRightContent();
6127 indentedParentElement = nullptr;
6128 firstContentToBeOutdented = nullptr;
6129 lastContentToBeOutdented = nullptr;
6130 indentedParentIndentedWith = BlockIndentedWith::HTML;
6132 Result<EditorDOMPoint, nsresult> unwrapBlockquoteElementResult =
6133 RemoveBlockContainerWithTransaction(
6134 MOZ_KnownLive(*content->AsElement()));
6135 if (MOZ_UNLIKELY(unwrapBlockquoteElementResult.isErr())) {
6136 NS_WARNING("HTMLEditor::RemoveBlockContainerWithTransaction() failed");
6137 return unwrapBlockquoteElementResult.propagateErr();
6139 const EditorDOMPoint& pointToPutCaret =
6140 unwrapBlockquoteElementResult.inspect();
6141 if (AllowsTransactionsToChangeSelection() && pointToPutCaret.IsSet()) {
6142 nsresult rv = CollapseSelectionTo(pointToPutCaret);
6143 if (NS_FAILED(rv)) {
6144 NS_WARNING("EditorBase::CollapseSelectionTo() failed");
6145 return Err(rv);
6148 continue;
6151 // If we're using CSS and the node is a block element, check its start
6152 // margin whether it's indented with CSS.
6153 if (useCSS && HTMLEditUtils::IsBlockElement(
6154 content, BlockInlineCheck::UseHTMLDefaultStyle)) {
6155 nsStaticAtom& marginProperty =
6156 MarginPropertyAtomForIndent(MOZ_KnownLive(content));
6157 if (NS_WARN_IF(Destroyed())) {
6158 return Err(NS_ERROR_EDITOR_DESTROYED);
6160 nsAutoString value;
6161 DebugOnly<nsresult> rvIgnored =
6162 CSSEditUtils::GetSpecifiedProperty(content, marginProperty, value);
6163 if (NS_WARN_IF(Destroyed())) {
6164 return Err(NS_ERROR_EDITOR_DESTROYED);
6166 NS_WARNING_ASSERTION(
6167 NS_SUCCEEDED(rvIgnored),
6168 "CSSEditUtils::GetSpecifiedProperty() failed, but ignored");
6169 float startMargin = 0;
6170 RefPtr<nsAtom> unit;
6171 CSSEditUtils::ParseLength(value, &startMargin, getter_AddRefs(unit));
6172 // If indented with CSS, we should decrease the start margin.
6173 if (startMargin > 0) {
6174 const Result<EditorDOMPoint, nsresult> pointToPutCaretOrError =
6175 ChangeMarginStart(MOZ_KnownLive(*content->AsElement()),
6176 ChangeMargin::Decrease, aEditingHost);
6177 if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) {
6178 if (NS_WARN_IF(pointToPutCaretOrError.inspectErr() ==
6179 NS_ERROR_EDITOR_DESTROYED)) {
6180 return Err(NS_ERROR_EDITOR_DESTROYED);
6182 NS_WARNING(
6183 "HTMLEditor::ChangeMarginStart(ChangeMargin::Decrease) failed, "
6184 "but ignored");
6185 } else if (AllowsTransactionsToChangeSelection() &&
6186 pointToPutCaretOrError.inspect().IsSet()) {
6187 nsresult rv = CollapseSelectionTo(pointToPutCaretOrError.inspect());
6188 if (NS_FAILED(rv)) {
6189 NS_WARNING("EditorBase::CollapseSelectionTo() failed");
6190 return Err(rv);
6193 continue;
6197 // If it's a list item, we should treat as that it "indents" its children.
6198 if (HTMLEditUtils::IsListItem(content)) {
6199 // If it is a list item, that means we are not outdenting whole list.
6200 // XXX I don't understand this sentence... We may meet parent list
6201 // element, no?
6202 if (indentedParentElement) {
6203 Result<SplitRangeOffFromNodeResult, nsresult> outdentResult =
6204 OutdentPartOfBlock(*indentedParentElement,
6205 *firstContentToBeOutdented,
6206 *lastContentToBeOutdented,
6207 indentedParentIndentedWith, aEditingHost);
6208 if (MOZ_UNLIKELY(outdentResult.isErr())) {
6209 NS_WARNING("HTMLEditor::OutdentPartOfBlock() failed");
6210 return outdentResult;
6212 SplitRangeOffFromNodeResult unwrappedOutdentResult =
6213 outdentResult.unwrap();
6214 unwrappedOutdentResult.IgnoreCaretPointSuggestion();
6215 leftContentOfLastOutdented = unwrappedOutdentResult.UnwrapLeftContent();
6216 middleContentOfLastOutdented =
6217 unwrappedOutdentResult.UnwrapMiddleContent();
6218 rightContentOfLastOutdented =
6219 unwrappedOutdentResult.UnwrapRightContent();
6220 indentedParentElement = nullptr;
6221 firstContentToBeOutdented = nullptr;
6222 lastContentToBeOutdented = nullptr;
6223 indentedParentIndentedWith = BlockIndentedWith::HTML;
6225 // XXX `content` could become different element since
6226 // `OutdentPartOfBlock()` may run mutation event listeners.
6227 nsresult rv = LiftUpListItemElement(MOZ_KnownLive(*content->AsElement()),
6228 LiftUpFromAllParentListElements::No);
6229 if (NS_FAILED(rv)) {
6230 NS_WARNING(
6231 "HTMLEditor::LiftUpListItemElement(LiftUpFromAllParentListElements:"
6232 ":No) failed");
6233 return Err(rv);
6235 continue;
6238 // If we've found an ancestor block element which indents its children
6239 // and the current node is NOT a descendant of it, we should remove it to
6240 // outdent its children. Otherwise, i.e., current node is a descendant of
6241 // it, we meet new node which should be outdented when the indented parent
6242 // is removed.
6243 if (indentedParentElement) {
6244 if (EditorUtils::IsDescendantOf(*content, *indentedParentElement)) {
6245 // Extend the range to be outdented at removing the
6246 // indentedParentElement.
6247 lastContentToBeOutdented = content;
6248 continue;
6250 Result<SplitRangeOffFromNodeResult, nsresult> outdentResult =
6251 OutdentPartOfBlock(*indentedParentElement, *firstContentToBeOutdented,
6252 *lastContentToBeOutdented,
6253 indentedParentIndentedWith, aEditingHost);
6254 if (MOZ_UNLIKELY(outdentResult.isErr())) {
6255 NS_WARNING("HTMLEditor::OutdentPartOfBlock() failed");
6256 return outdentResult;
6258 SplitRangeOffFromNodeResult unwrappedOutdentResult =
6259 outdentResult.unwrap();
6260 unwrappedOutdentResult.IgnoreCaretPointSuggestion();
6261 leftContentOfLastOutdented = unwrappedOutdentResult.UnwrapLeftContent();
6262 middleContentOfLastOutdented =
6263 unwrappedOutdentResult.UnwrapMiddleContent();
6264 rightContentOfLastOutdented = unwrappedOutdentResult.UnwrapRightContent();
6265 indentedParentElement = nullptr;
6266 firstContentToBeOutdented = nullptr;
6267 lastContentToBeOutdented = nullptr;
6268 // curBlockIndentedWith = HTMLEditor::BlockIndentedWith::HTML;
6270 // Then, we need to look for next indentedParentElement.
6273 indentedParentIndentedWith = BlockIndentedWith::HTML;
6274 for (nsCOMPtr<nsIContent> parentContent = content->GetParent();
6275 parentContent && !parentContent->IsHTMLElement(nsGkAtoms::body) &&
6276 parentContent != &aEditingHost &&
6277 (parentContent->IsHTMLElement(nsGkAtoms::table) ||
6278 !HTMLEditUtils::IsAnyTableElement(parentContent));
6279 parentContent = parentContent->GetParent()) {
6280 if (MOZ_UNLIKELY(!HTMLEditUtils::IsRemovableNode(*parentContent))) {
6281 continue;
6283 // If we reach a `<blockquote>` ancestor, it should be split at next
6284 // time at least for outdenting current node.
6285 if (parentContent->IsHTMLElement(nsGkAtoms::blockquote)) {
6286 indentedParentElement = parentContent->AsElement();
6287 firstContentToBeOutdented = content;
6288 lastContentToBeOutdented = content;
6289 break;
6292 if (!useCSS) {
6293 continue;
6296 nsCOMPtr<nsINode> grandParentNode = parentContent->GetParentNode();
6297 nsStaticAtom& marginProperty =
6298 MarginPropertyAtomForIndent(MOZ_KnownLive(content));
6299 if (NS_WARN_IF(Destroyed())) {
6300 return Err(NS_ERROR_EDITOR_DESTROYED);
6302 if (NS_WARN_IF(grandParentNode != parentContent->GetParentNode())) {
6303 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
6305 nsAutoString value;
6306 DebugOnly<nsresult> rvIgnored = CSSEditUtils::GetSpecifiedProperty(
6307 *parentContent, marginProperty, value);
6308 if (NS_WARN_IF(Destroyed())) {
6309 return Err(NS_ERROR_EDITOR_DESTROYED);
6311 NS_WARNING_ASSERTION(
6312 NS_SUCCEEDED(rvIgnored),
6313 "CSSEditUtils::GetSpecifiedProperty() failed, but ignored");
6314 // XXX Now, editing host may become different element. If so, shouldn't
6315 // we stop this handling?
6316 float startMargin;
6317 RefPtr<nsAtom> unit;
6318 CSSEditUtils::ParseLength(value, &startMargin, getter_AddRefs(unit));
6319 // If we reach a block element which indents its children with start
6320 // margin, we should remove it at next time.
6321 if (startMargin > 0 &&
6322 !(HTMLEditUtils::IsAnyListElement(atContent.GetContainer()) &&
6323 HTMLEditUtils::IsAnyListElement(content))) {
6324 indentedParentElement = parentContent->AsElement();
6325 firstContentToBeOutdented = content;
6326 lastContentToBeOutdented = content;
6327 indentedParentIndentedWith = BlockIndentedWith::CSS;
6328 break;
6332 if (indentedParentElement) {
6333 continue;
6336 // If we don't have any block elements which indents current node and
6337 // both current node and its parent are list element, remove current
6338 // node to move all its children to the parent list.
6339 // XXX This is buggy. When both lists' item types are different,
6340 // we create invalid tree. E.g., `<ul>` may have `<dd>` as its
6341 // list item element.
6342 if (HTMLEditUtils::IsAnyListElement(atContent.GetContainer())) {
6343 if (!HTMLEditUtils::IsAnyListElement(content)) {
6344 continue;
6346 // Just unwrap this sublist
6347 Result<EditorDOMPoint, nsresult> unwrapSubListElementResult =
6348 RemoveBlockContainerWithTransaction(
6349 MOZ_KnownLive(*content->AsElement()));
6350 if (MOZ_UNLIKELY(unwrapSubListElementResult.isErr())) {
6351 NS_WARNING("HTMLEditor::RemoveBlockContainerWithTransaction() failed");
6352 return unwrapSubListElementResult.propagateErr();
6354 const EditorDOMPoint& pointToPutCaret =
6355 unwrapSubListElementResult.inspect();
6356 if (!AllowsTransactionsToChangeSelection() || !pointToPutCaret.IsSet()) {
6357 continue;
6359 nsresult rv = CollapseSelectionTo(pointToPutCaret);
6360 if (NS_FAILED(rv)) {
6361 NS_WARNING("EditorBase::CollapseSelectionTo() failed");
6362 return Err(rv);
6364 continue;
6367 // If current content is a list element but its parent is not a list
6368 // element, move children to where it is and remove it from the tree.
6369 if (HTMLEditUtils::IsAnyListElement(content)) {
6370 // XXX If mutation event listener appends new children forever, this
6371 // becomes an infinite loop so that we should set limitation from
6372 // first child count.
6373 for (nsCOMPtr<nsIContent> lastChildContent = content->GetLastChild();
6374 lastChildContent; lastChildContent = content->GetLastChild()) {
6375 if (HTMLEditUtils::IsListItem(lastChildContent)) {
6376 nsresult rv = LiftUpListItemElement(
6377 MOZ_KnownLive(*lastChildContent->AsElement()),
6378 LiftUpFromAllParentListElements::No);
6379 if (NS_FAILED(rv)) {
6380 NS_WARNING(
6381 "HTMLEditor::LiftUpListItemElement("
6382 "LiftUpFromAllParentListElements::No) failed");
6383 return Err(rv);
6385 continue;
6388 if (HTMLEditUtils::IsAnyListElement(lastChildContent)) {
6389 // We have an embedded list, so move it out from under the parent
6390 // list. Be sure to put it after the parent list because this
6391 // loop iterates backwards through the parent's list of children.
6392 EditorDOMPoint afterCurrentList(EditorDOMPoint::After(atContent));
6393 NS_WARNING_ASSERTION(
6394 afterCurrentList.IsSet(),
6395 "Failed to set it to after current list element");
6396 Result<MoveNodeResult, nsresult> moveListElementResult =
6397 MoveNodeWithTransaction(*lastChildContent, afterCurrentList);
6398 if (MOZ_UNLIKELY(moveListElementResult.isErr())) {
6399 NS_WARNING("HTMLEditor::MoveNodeWithTransaction() failed");
6400 return moveListElementResult.propagateErr();
6402 nsresult rv = moveListElementResult.inspect().SuggestCaretPointTo(
6403 *this, {SuggestCaret::OnlyIfHasSuggestion,
6404 SuggestCaret::OnlyIfTransactionsAllowedToDoIt,
6405 SuggestCaret::AndIgnoreTrivialError});
6406 if (NS_FAILED(rv)) {
6407 NS_WARNING("MoveNodeResult::SuggestCaretPointTo() failed");
6408 return Err(rv);
6410 NS_WARNING_ASSERTION(
6411 rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
6412 "MoveNodeResult::SuggestCaretPointTo() failed, but ignored");
6413 continue;
6416 // Delete any non-list items for now
6417 // XXX Chrome moves it from the list element. We should follow it.
6418 nsresult rv = DeleteNodeWithTransaction(*lastChildContent);
6419 if (NS_FAILED(rv)) {
6420 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
6421 return Err(rv);
6424 // Delete the now-empty list
6425 Result<EditorDOMPoint, nsresult> unwrapListElementResult =
6426 RemoveBlockContainerWithTransaction(
6427 MOZ_KnownLive(*content->AsElement()));
6428 if (MOZ_UNLIKELY(unwrapListElementResult.isErr())) {
6429 NS_WARNING("HTMLEditor::RemoveBlockContainerWithTransaction() failed");
6430 return unwrapListElementResult.propagateErr();
6432 const EditorDOMPoint& pointToPutCaret = unwrapListElementResult.inspect();
6433 if (!AllowsTransactionsToChangeSelection() || !pointToPutCaret.IsSet()) {
6434 continue;
6436 nsresult rv = CollapseSelectionTo(pointToPutCaret);
6437 if (NS_FAILED(rv)) {
6438 NS_WARNING("EditorBase::CollapseSelectionTo() failed");
6439 return Err(rv);
6441 continue;
6444 if (useCSS) {
6445 if (RefPtr<Element> element = content->GetAsElementOrParentElement()) {
6446 const Result<EditorDOMPoint, nsresult> pointToPutCaretOrError =
6447 ChangeMarginStart(*element, ChangeMargin::Decrease, aEditingHost);
6448 if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) {
6449 if (NS_WARN_IF(pointToPutCaretOrError.inspectErr() ==
6450 NS_ERROR_EDITOR_DESTROYED)) {
6451 return Err(NS_ERROR_EDITOR_DESTROYED);
6453 NS_WARNING(
6454 "HTMLEditor::ChangeMarginStart(ChangeMargin::Decrease) failed, "
6455 "but ignored");
6456 } else if (AllowsTransactionsToChangeSelection() &&
6457 pointToPutCaretOrError.inspect().IsSet()) {
6458 nsresult rv = CollapseSelectionTo(pointToPutCaretOrError.inspect());
6459 if (NS_FAILED(rv)) {
6460 NS_WARNING("EditorBase::CollapseSelectionTo() failed");
6461 return Err(rv);
6465 continue;
6469 if (!indentedParentElement) {
6470 return SplitRangeOffFromNodeResult(leftContentOfLastOutdented,
6471 middleContentOfLastOutdented,
6472 rightContentOfLastOutdented);
6475 // We have a <blockquote> we haven't finished handling.
6476 Result<SplitRangeOffFromNodeResult, nsresult> outdentResult =
6477 OutdentPartOfBlock(*indentedParentElement, *firstContentToBeOutdented,
6478 *lastContentToBeOutdented, indentedParentIndentedWith,
6479 aEditingHost);
6480 if (MOZ_UNLIKELY(outdentResult.isErr())) {
6481 NS_WARNING("HTMLEditor::OutdentPartOfBlock() failed");
6482 return outdentResult;
6484 // We will restore selection soon. Therefore, callers do not need to restore
6485 // the selection.
6486 SplitRangeOffFromNodeResult unwrappedOutdentResult = outdentResult.unwrap();
6487 unwrappedOutdentResult.ForgetCaretPointSuggestion();
6488 return unwrappedOutdentResult;
6491 Result<SplitRangeOffFromNodeResult, nsresult>
6492 HTMLEditor::RemoveBlockContainerElementWithTransactionBetween(
6493 Element& aBlockContainerElement, nsIContent& aStartOfRange,
6494 nsIContent& aEndOfRange, BlockInlineCheck aBlockInlineCheck) {
6495 MOZ_ASSERT(IsEditActionDataAvailable());
6497 EditorDOMPoint pointToPutCaret;
6498 Result<SplitRangeOffFromNodeResult, nsresult> splitResult =
6499 SplitRangeOffFromElement(aBlockContainerElement, aStartOfRange,
6500 aEndOfRange);
6501 if (MOZ_UNLIKELY(splitResult.isErr())) {
6502 if (splitResult.inspectErr() == NS_ERROR_EDITOR_DESTROYED) {
6503 NS_WARNING("HTMLEditor::SplitRangeOffFromElement() failed");
6504 return splitResult;
6506 NS_WARNING(
6507 "HTMLEditor::SplitRangeOffFromElement() failed, but might be ignored");
6508 return SplitRangeOffFromNodeResult(nullptr, nullptr, nullptr);
6510 SplitRangeOffFromNodeResult unwrappedSplitResult = splitResult.unwrap();
6511 unwrappedSplitResult.MoveCaretPointTo(pointToPutCaret,
6512 {SuggestCaret::OnlyIfHasSuggestion});
6514 // Even if either split aBlockContainerElement or did not split it, we should
6515 // unwrap the right most element which is split from aBlockContainerElement
6516 // (or aBlockContainerElement itself if it was not split without errors).
6517 Element* rightmostElement =
6518 unwrappedSplitResult.GetRightmostContentAs<Element>();
6519 MOZ_ASSERT(rightmostElement);
6520 if (NS_WARN_IF(!rightmostElement)) {
6521 return Err(NS_ERROR_FAILURE);
6525 // MOZ_KnownLive(rightmostElement) because it's grabbed by
6526 // unwrappedSplitResult.
6527 Result<EditorDOMPoint, nsresult> unwrapBlockElementResult =
6528 RemoveBlockContainerWithTransaction(MOZ_KnownLive(*rightmostElement));
6529 if (MOZ_UNLIKELY(unwrapBlockElementResult.isErr())) {
6530 NS_WARNING("HTMLEditor::RemoveBlockContainerWithTransaction() failed");
6531 return unwrapBlockElementResult.propagateErr();
6533 if (unwrapBlockElementResult.inspect().IsSet()) {
6534 pointToPutCaret = unwrapBlockElementResult.unwrap();
6538 return SplitRangeOffFromNodeResult(
6539 unwrappedSplitResult.GetLeftContent(), nullptr,
6540 unwrappedSplitResult.GetRightContent(), std::move(pointToPutCaret));
6543 Result<SplitRangeOffFromNodeResult, nsresult>
6544 HTMLEditor::SplitRangeOffFromElement(Element& aElementToSplit,
6545 nsIContent& aStartOfMiddleElement,
6546 nsIContent& aEndOfMiddleElement) {
6547 MOZ_ASSERT(IsEditActionDataAvailable());
6549 // aStartOfMiddleElement and aEndOfMiddleElement must be exclusive
6550 // descendants of aElementToSplit.
6551 MOZ_ASSERT(
6552 EditorUtils::IsDescendantOf(aStartOfMiddleElement, aElementToSplit));
6553 MOZ_ASSERT(EditorUtils::IsDescendantOf(aEndOfMiddleElement, aElementToSplit));
6555 EditorDOMPoint pointToPutCaret;
6556 // Split at the start.
6557 Result<SplitNodeResult, nsresult> splitAtStartResult =
6558 SplitNodeDeepWithTransaction(aElementToSplit,
6559 EditorDOMPoint(&aStartOfMiddleElement),
6560 SplitAtEdges::eDoNotCreateEmptyContainer);
6561 if (MOZ_UNLIKELY(splitAtStartResult.isErr())) {
6562 if (splitAtStartResult.inspectErr() == NS_ERROR_EDITOR_DESTROYED) {
6563 NS_WARNING("HTMLEditor::SplitNodeDeepWithTransaction() failed (at left)");
6564 return Err(NS_ERROR_EDITOR_DESTROYED);
6566 NS_WARNING(
6567 "HTMLEditor::SplitNodeDeepWithTransaction(SplitAtEdges::"
6568 "eDoNotCreateEmptyContainer) at start of middle element failed");
6569 } else {
6570 splitAtStartResult.inspect().CopyCaretPointTo(
6571 pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
6574 // Split at after the end
6575 auto atAfterEnd = EditorDOMPoint::After(aEndOfMiddleElement);
6576 Element* rightElement =
6577 splitAtStartResult.isOk() && splitAtStartResult.inspect().DidSplit()
6578 ? splitAtStartResult.inspect().GetNextContentAs<Element>()
6579 : &aElementToSplit;
6580 // MOZ_KnownLive(rightElement) because it's grabbed by splitAtStartResult or
6581 // aElementToSplit whose lifetime is guaranteed by the caller.
6582 Result<SplitNodeResult, nsresult> splitAtEndResult =
6583 SplitNodeDeepWithTransaction(MOZ_KnownLive(*rightElement), atAfterEnd,
6584 SplitAtEdges::eDoNotCreateEmptyContainer);
6585 if (MOZ_UNLIKELY(splitAtEndResult.isErr())) {
6586 if (splitAtEndResult.inspectErr() == NS_ERROR_EDITOR_DESTROYED) {
6587 NS_WARNING(
6588 "HTMLEditor::SplitNodeDeepWithTransaction() failed (at right)");
6589 return Err(NS_ERROR_EDITOR_DESTROYED);
6591 NS_WARNING(
6592 "HTMLEditor::SplitNodeDeepWithTransaction(SplitAtEdges::"
6593 "eDoNotCreateEmptyContainer) after end of middle element failed");
6594 } else {
6595 splitAtEndResult.inspect().CopyCaretPointTo(
6596 pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
6599 if (splitAtStartResult.isOk() && splitAtStartResult.inspect().DidSplit() &&
6600 splitAtEndResult.isOk() && splitAtEndResult.inspect().DidSplit()) {
6601 // Note that the middle node can be computed only with the latter split
6602 // result.
6603 return SplitRangeOffFromNodeResult(
6604 splitAtStartResult.inspect().GetPreviousContent(),
6605 splitAtEndResult.inspect().GetPreviousContent(),
6606 splitAtEndResult.inspect().GetNextContent(),
6607 std::move(pointToPutCaret));
6609 if (splitAtStartResult.isOk() && splitAtStartResult.inspect().DidSplit()) {
6610 return SplitRangeOffFromNodeResult(
6611 splitAtStartResult.inspect().GetPreviousContent(),
6612 splitAtStartResult.inspect().GetNextContent(), nullptr,
6613 std::move(pointToPutCaret));
6615 if (splitAtEndResult.isOk() && splitAtEndResult.inspect().DidSplit()) {
6616 return SplitRangeOffFromNodeResult(
6617 nullptr, splitAtEndResult.inspect().GetPreviousContent(),
6618 splitAtEndResult.inspect().GetNextContent(),
6619 std::move(pointToPutCaret));
6621 return SplitRangeOffFromNodeResult(nullptr, &aElementToSplit, nullptr,
6622 std::move(pointToPutCaret));
6625 Result<SplitRangeOffFromNodeResult, nsresult> HTMLEditor::OutdentPartOfBlock(
6626 Element& aBlockElement, nsIContent& aStartOfOutdent,
6627 nsIContent& aEndOfOutdent, BlockIndentedWith aBlockIndentedWith,
6628 const Element& aEditingHost) {
6629 MOZ_ASSERT(IsEditActionDataAvailable());
6631 Result<SplitRangeOffFromNodeResult, nsresult> splitResult =
6632 SplitRangeOffFromElement(aBlockElement, aStartOfOutdent, aEndOfOutdent);
6633 if (MOZ_UNLIKELY(splitResult.isErr())) {
6634 NS_WARNING("HTMLEditor::SplitRangeOffFromElement() failed");
6635 return splitResult;
6638 SplitRangeOffFromNodeResult unwrappedSplitResult = splitResult.unwrap();
6639 Element* middleElement = unwrappedSplitResult.GetMiddleContentAs<Element>();
6640 if (MOZ_UNLIKELY(!middleElement)) {
6641 NS_WARNING(
6642 "HTMLEditor::SplitRangeOffFromElement() didn't return middle content");
6643 unwrappedSplitResult.IgnoreCaretPointSuggestion();
6644 return Err(NS_ERROR_FAILURE);
6646 if (NS_WARN_IF(!HTMLEditUtils::IsRemovableNode(*middleElement))) {
6647 unwrappedSplitResult.IgnoreCaretPointSuggestion();
6648 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
6651 nsresult rv = unwrappedSplitResult.SuggestCaretPointTo(
6652 *this, {SuggestCaret::OnlyIfHasSuggestion,
6653 SuggestCaret::OnlyIfTransactionsAllowedToDoIt,
6654 SuggestCaret::AndIgnoreTrivialError});
6655 if (NS_FAILED(rv)) {
6656 NS_WARNING("SplitRangeOffFromNodeResult::SuggestCaretPointTo() failed");
6657 return Err(rv);
6659 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
6660 "SplitRangeOffFromNodeResult::SuggestCaretPointTo() "
6661 "failed, but ignored");
6663 if (aBlockIndentedWith == BlockIndentedWith::HTML) {
6664 // MOZ_KnownLive(middleElement) because of grabbed by unwrappedSplitResult.
6665 Result<EditorDOMPoint, nsresult> unwrapBlockElementResult =
6666 RemoveBlockContainerWithTransaction(MOZ_KnownLive(*middleElement));
6667 if (MOZ_UNLIKELY(unwrapBlockElementResult.isErr())) {
6668 NS_WARNING("HTMLEditor::RemoveBlockContainerWithTransaction() failed");
6669 return unwrapBlockElementResult.propagateErr();
6671 const EditorDOMPoint& pointToPutCaret = unwrapBlockElementResult.inspect();
6672 if (AllowsTransactionsToChangeSelection() && pointToPutCaret.IsSet()) {
6673 nsresult rv = CollapseSelectionTo(pointToPutCaret);
6674 if (NS_FAILED(rv)) {
6675 NS_WARNING("EditorBase::CollapseSelectionTo() failed");
6676 return Err(rv);
6679 return SplitRangeOffFromNodeResult(unwrappedSplitResult.GetLeftContent(),
6680 nullptr,
6681 unwrappedSplitResult.GetRightContent());
6684 // MOZ_KnownLive(middleElement) because of grabbed by unwrappedSplitResult.
6685 Result<EditorDOMPoint, nsresult> pointToPutCaretOrError = ChangeMarginStart(
6686 MOZ_KnownLive(*middleElement), ChangeMargin::Decrease, aEditingHost);
6687 if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) {
6688 NS_WARNING("HTMLEditor::ChangeMarginStart(ChangeMargin::Decrease) failed");
6689 return pointToPutCaretOrError.propagateErr();
6691 if (AllowsTransactionsToChangeSelection() &&
6692 pointToPutCaretOrError.inspect().IsSet()) {
6693 nsresult rv = CollapseSelectionTo(pointToPutCaretOrError.inspect());
6694 if (NS_FAILED(rv)) {
6695 NS_WARNING("EditorBase::CollapseSelectionTo() failed");
6696 return Err(rv);
6699 return unwrappedSplitResult;
6702 Result<CreateElementResult, nsresult> HTMLEditor::ChangeListElementType(
6703 Element& aListElement, nsAtom& aNewListTag, nsAtom& aNewListItemTag) {
6704 MOZ_ASSERT(IsEditActionDataAvailable());
6706 EditorDOMPoint pointToPutCaret;
6708 AutoTArray<OwningNonNull<nsIContent>, 32> listElementChildren;
6709 HTMLEditUtils::CollectAllChildren(aListElement, listElementChildren);
6711 for (const OwningNonNull<nsIContent>& childContent : listElementChildren) {
6712 if (!childContent->IsElement()) {
6713 continue;
6715 Element* childElement = childContent->AsElement();
6716 if (HTMLEditUtils::IsListItem(childElement) &&
6717 !childContent->IsHTMLElement(&aNewListItemTag)) {
6718 // MOZ_KnownLive(childElement) because its lifetime is guaranteed by
6719 // listElementChildren.
6720 Result<CreateElementResult, nsresult>
6721 replaceWithNewListItemElementResult =
6722 ReplaceContainerAndCloneAttributesWithTransaction(
6723 MOZ_KnownLive(*childElement), aNewListItemTag);
6724 if (MOZ_UNLIKELY(replaceWithNewListItemElementResult.isErr())) {
6725 NS_WARNING(
6726 "HTMLEditor::ReplaceContainerAndCloneAttributesWithTransaction() "
6727 "failed");
6728 return replaceWithNewListItemElementResult;
6730 CreateElementResult unwrappedReplaceWithNewListItemElementResult =
6731 replaceWithNewListItemElementResult.unwrap();
6732 unwrappedReplaceWithNewListItemElementResult.MoveCaretPointTo(
6733 pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
6734 continue;
6736 if (HTMLEditUtils::IsAnyListElement(childElement) &&
6737 !childElement->IsHTMLElement(&aNewListTag)) {
6738 // XXX List elements shouldn't have other list elements as their
6739 // child. Why do we handle such invalid tree?
6740 // -> Maybe, for bug 525888.
6741 // MOZ_KnownLive(childElement) because its lifetime is guaranteed by
6742 // listElementChildren.
6743 Result<CreateElementResult, nsresult> convertListTypeResult =
6744 ChangeListElementType(MOZ_KnownLive(*childElement), aNewListTag,
6745 aNewListItemTag);
6746 if (MOZ_UNLIKELY(convertListTypeResult.isErr())) {
6747 NS_WARNING("HTMLEditor::ChangeListElementType() failed");
6748 return convertListTypeResult;
6750 CreateElementResult unwrappedConvertListTypeResult =
6751 convertListTypeResult.unwrap();
6752 unwrappedConvertListTypeResult.MoveCaretPointTo(
6753 pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
6754 continue;
6758 if (aListElement.IsHTMLElement(&aNewListTag)) {
6759 return CreateElementResult(&aListElement, std::move(pointToPutCaret));
6762 // XXX If we replace the list element, shouldn't we create it first and then,
6763 // move children into it before inserting the new list element into the
6764 // DOM tree? Then, we could reduce the cost of dispatching DOM mutation
6765 // events.
6766 Result<CreateElementResult, nsresult> replaceWithNewListElementResult =
6767 ReplaceContainerWithTransaction(aListElement, aNewListTag);
6768 if (MOZ_UNLIKELY(replaceWithNewListElementResult.isErr())) {
6769 NS_WARNING("HTMLEditor::ReplaceContainerWithTransaction() failed");
6770 return replaceWithNewListElementResult;
6772 CreateElementResult unwrappedReplaceWithNewListElementResult =
6773 replaceWithNewListElementResult.unwrap();
6774 unwrappedReplaceWithNewListElementResult.MoveCaretPointTo(
6775 pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
6776 return CreateElementResult(
6777 unwrappedReplaceWithNewListElementResult.UnwrapNewNode(),
6778 std::move(pointToPutCaret));
6781 Result<EditorDOMPoint, nsresult> HTMLEditor::CreateStyleForInsertText(
6782 const EditorDOMPoint& aPointToInsertText, const Element& aEditingHost) {
6783 MOZ_ASSERT(IsEditActionDataAvailable());
6784 MOZ_ASSERT(aPointToInsertText.IsSetAndValid());
6785 MOZ_ASSERT(mPendingStylesToApplyToNewContent);
6787 const RefPtr<Element> documentRootElement = GetDocument()->GetRootElement();
6788 if (NS_WARN_IF(!documentRootElement)) {
6789 return Err(NS_ERROR_FAILURE);
6792 // process clearing any styles first
6793 UniquePtr<PendingStyle> pendingStyle =
6794 mPendingStylesToApplyToNewContent->TakeClearingStyle();
6796 EditorDOMPoint pointToPutCaret(aPointToInsertText);
6798 // Transactions may set selection, but we will set selection if necessary.
6799 AutoTransactionsConserveSelection dontChangeMySelection(*this);
6801 while (pendingStyle &&
6802 pointToPutCaret.GetContainer() != documentRootElement) {
6803 // MOZ_KnownLive because we own pendingStyle which guarantees the lifetime
6804 // of its members.
6805 Result<EditorDOMPoint, nsresult> pointToPutCaretOrError =
6806 ClearStyleAt(pointToPutCaret, pendingStyle->ToInlineStyle(),
6807 pendingStyle->GetSpecifiedStyle());
6808 if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) {
6809 NS_WARNING("HTMLEditor::ClearStyleAt() failed");
6810 return pointToPutCaretOrError;
6812 pointToPutCaret = pointToPutCaretOrError.unwrap();
6813 if (NS_WARN_IF(!pointToPutCaret.IsSetAndValid())) {
6814 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
6816 pendingStyle = mPendingStylesToApplyToNewContent->TakeClearingStyle();
6820 // then process setting any styles
6821 const int32_t relFontSize =
6822 mPendingStylesToApplyToNewContent->TakeRelativeFontSize();
6823 AutoTArray<EditorInlineStyleAndValue, 32> stylesToSet;
6824 mPendingStylesToApplyToNewContent->TakeAllPreservedStyles(stylesToSet);
6825 if (stylesToSet.IsEmpty() && !relFontSize) {
6826 return pointToPutCaret;
6829 // We're in chrome, e.g., the email composer of Thunderbird, and there is
6830 // relative font size changes, we need to keep using legacy path until we port
6831 // IncrementOrDecrementFontSizeAsSubAction() to work with
6832 // AutoInlineStyleSetter.
6833 if (relFontSize) {
6834 // we have at least one style to add; make a new text node to insert style
6835 // nodes above.
6836 EditorDOMPoint pointToInsertTextNode(pointToPutCaret);
6837 if (pointToInsertTextNode.IsInTextNode()) {
6838 // if we are in a text node, split it
6839 Result<SplitNodeResult, nsresult> splitTextNodeResult =
6840 SplitNodeDeepWithTransaction(
6841 MOZ_KnownLive(*pointToInsertTextNode.ContainerAs<Text>()),
6842 pointToInsertTextNode,
6843 SplitAtEdges::eAllowToCreateEmptyContainer);
6844 if (MOZ_UNLIKELY(splitTextNodeResult.isErr())) {
6845 NS_WARNING(
6846 "HTMLEditor::SplitNodeDeepWithTransaction(SplitAtEdges::"
6847 "eAllowToCreateEmptyContainer) failed");
6848 return splitTextNodeResult.propagateErr();
6850 SplitNodeResult unwrappedSplitTextNodeResult =
6851 splitTextNodeResult.unwrap();
6852 unwrappedSplitTextNodeResult.MoveCaretPointTo(
6853 pointToPutCaret, *this,
6854 {SuggestCaret::OnlyIfHasSuggestion,
6855 SuggestCaret::OnlyIfTransactionsAllowedToDoIt});
6856 pointToInsertTextNode =
6857 unwrappedSplitTextNodeResult.AtSplitPoint<EditorDOMPoint>();
6859 if (!pointToInsertTextNode.IsInContentNode() ||
6860 !HTMLEditUtils::IsContainerNode(
6861 *pointToInsertTextNode.ContainerAs<nsIContent>())) {
6862 return pointToPutCaret;
6864 RefPtr<Text> newEmptyTextNode = CreateTextNode(u""_ns);
6865 if (!newEmptyTextNode) {
6866 NS_WARNING("EditorBase::CreateTextNode() failed");
6867 return Err(NS_ERROR_FAILURE);
6869 Result<CreateTextResult, nsresult> insertNewTextNodeResult =
6870 InsertNodeWithTransaction<Text>(*newEmptyTextNode,
6871 pointToInsertTextNode);
6872 if (MOZ_UNLIKELY(insertNewTextNodeResult.isErr())) {
6873 NS_WARNING("EditorBase::InsertNodeWithTransaction() failed");
6874 return insertNewTextNodeResult.propagateErr();
6876 insertNewTextNodeResult.inspect().IgnoreCaretPointSuggestion();
6877 pointToPutCaret.Set(newEmptyTextNode, 0u);
6879 // FIXME: If the stylesToSet have background-color style, it may
6880 // be applied shorter because outer <span> element height is not
6881 // computed with inner element's height.
6882 HTMLEditor::FontSize incrementOrDecrement =
6883 relFontSize > 0 ? HTMLEditor::FontSize::incr
6884 : HTMLEditor::FontSize::decr;
6885 for ([[maybe_unused]] uint32_t j : IntegerRange(Abs(relFontSize))) {
6886 Result<CreateElementResult, nsresult> wrapTextInBigOrSmallElementResult =
6887 SetFontSizeOnTextNode(*newEmptyTextNode, 0, UINT32_MAX,
6888 incrementOrDecrement);
6889 if (MOZ_UNLIKELY(wrapTextInBigOrSmallElementResult.isErr())) {
6890 NS_WARNING("HTMLEditor::SetFontSizeOnTextNode() failed");
6891 return wrapTextInBigOrSmallElementResult.propagateErr();
6893 // We don't need to update here because we'll suggest caret position
6894 // which is computed above.
6895 MOZ_ASSERT(pointToPutCaret.IsSet());
6896 wrapTextInBigOrSmallElementResult.inspect().IgnoreCaretPointSuggestion();
6899 for (const EditorInlineStyleAndValue& styleToSet : stylesToSet) {
6900 AutoInlineStyleSetter inlineStyleSetter(styleToSet);
6901 // MOZ_KnownLive(...ContainerAs<nsIContent>()) because pointToPutCaret
6902 // grabs the result.
6903 Result<CaretPoint, nsresult> setStyleResult =
6904 inlineStyleSetter.ApplyStyleToNodeOrChildrenAndRemoveNestedSameStyle(
6905 *this, MOZ_KnownLive(*pointToPutCaret.ContainerAs<nsIContent>()));
6906 if (MOZ_UNLIKELY(setStyleResult.isErr())) {
6907 NS_WARNING("HTMLEditor::SetInlinePropertyOnNode() failed");
6908 return setStyleResult.propagateErr();
6910 // We don't need to update here because we'll suggest caret position which
6911 // is computed above.
6912 MOZ_ASSERT(pointToPutCaret.IsSet());
6913 setStyleResult.unwrap().IgnoreCaretPointSuggestion();
6915 return pointToPutCaret;
6918 // If we have preserved commands except relative font style changes, we can
6919 // use inline style setting code which reuse ancestors better.
6920 AutoRangeArray ranges(pointToPutCaret);
6921 if (MOZ_UNLIKELY(ranges.Ranges().IsEmpty())) {
6922 NS_WARNING("AutoRangeArray::AutoRangeArray() failed");
6923 return Err(NS_ERROR_FAILURE);
6925 nsresult rv =
6926 SetInlinePropertiesAroundRanges(ranges, stylesToSet, aEditingHost);
6927 if (NS_FAILED(rv)) {
6928 NS_WARNING("HTMLEditor::SetInlinePropertiesAroundRanges() failed");
6929 return Err(rv);
6931 if (NS_WARN_IF(ranges.Ranges().IsEmpty())) {
6932 return Err(NS_ERROR_FAILURE);
6934 // Now `ranges` selects new styled contents and the range may not be
6935 // collapsed. We should use the deepest editable start point of the range
6936 // to insert text.
6937 nsINode* container = ranges.FirstRangeRef()->GetStartContainer();
6938 if (MOZ_UNLIKELY(!container->IsContent())) {
6939 container = ranges.FirstRangeRef()->GetChildAtStartOffset();
6940 if (MOZ_UNLIKELY(!container)) {
6941 NS_WARNING("How did we get lost insertion point?");
6942 return Err(NS_ERROR_FAILURE);
6945 pointToPutCaret =
6946 HTMLEditUtils::GetDeepestEditableStartPointOf<EditorDOMPoint>(
6947 *container->AsContent());
6948 if (NS_WARN_IF(!pointToPutCaret.IsSet())) {
6949 return Err(NS_ERROR_FAILURE);
6951 return pointToPutCaret;
6954 Result<EditActionResult, nsresult> HTMLEditor::AlignAsSubAction(
6955 const nsAString& aAlignType, const Element& aEditingHost) {
6956 MOZ_ASSERT(IsEditActionDataAvailable());
6958 AutoPlaceholderBatch treatAsOneTransaction(
6959 *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
6960 IgnoredErrorResult ignoredError;
6961 AutoEditSubActionNotifier startToHandleEditSubAction(
6962 *this, EditSubAction::eSetOrClearAlignment, nsIEditor::eNext,
6963 ignoredError);
6964 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
6965 return Err(ignoredError.StealNSResult());
6967 NS_WARNING_ASSERTION(
6968 !ignoredError.Failed(),
6969 "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
6972 Result<EditActionResult, nsresult> result = CanHandleHTMLEditSubAction();
6973 if (MOZ_UNLIKELY(result.isErr())) {
6974 NS_WARNING("HTMLEditor::CanHandleHTMLEditSubAction() failed");
6975 return result;
6977 if (result.inspect().Canceled()) {
6978 return result;
6982 if (MOZ_UNLIKELY(IsSelectionRangeContainerNotContent())) {
6983 NS_WARNING("Some selection containers are not content node, but ignored");
6984 return EditActionResult::IgnoredResult();
6987 nsresult rv = EnsureNoPaddingBRElementForEmptyEditor();
6988 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
6989 return Err(NS_ERROR_EDITOR_DESTROYED);
6991 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
6992 "EditorBase::EnsureNoPaddingBRElementForEmptyEditor() "
6993 "failed, but ignored");
6995 if (MOZ_UNLIKELY(IsSelectionRangeContainerNotContent())) {
6996 NS_WARNING("Mutation event listener might have changed the selection");
6997 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
7000 if (NS_SUCCEEDED(rv) && SelectionRef().IsCollapsed()) {
7001 nsresult rv = EnsureCaretNotAfterInvisibleBRElement();
7002 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
7003 return Err(NS_ERROR_EDITOR_DESTROYED);
7005 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
7006 "HTMLEditor::EnsureCaretNotAfterInvisibleBRElement() "
7007 "failed, but ignored");
7008 if (NS_SUCCEEDED(rv)) {
7009 nsresult rv = PrepareInlineStylesForCaret();
7010 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
7011 return Err(NS_ERROR_EDITOR_DESTROYED);
7013 NS_WARNING_ASSERTION(
7014 NS_SUCCEEDED(rv),
7015 "HTMLEditor::PrepareInlineStylesForCaret() failed, but ignored");
7019 AutoRangeArray selectionRanges(SelectionRef());
7021 // XXX Why do we do this only when there is only one selection range?
7022 if (!selectionRanges.IsCollapsed() &&
7023 selectionRanges.Ranges().Length() == 1u) {
7024 Result<EditorRawDOMRange, nsresult> extendedRange =
7025 GetRangeExtendedToHardLineEdgesForBlockEditAction(
7026 selectionRanges.FirstRangeRef(), aEditingHost);
7027 if (MOZ_UNLIKELY(extendedRange.isErr())) {
7028 NS_WARNING(
7029 "HTMLEditor::GetRangeExtendedToHardLineEdgesForBlockEditAction() "
7030 "failed");
7031 return extendedRange.propagateErr();
7033 // Note that end point may be prior to start point. So, we
7034 // cannot use etStartAndEnd() here.
7035 nsresult rv = selectionRanges.SetBaseAndExtent(
7036 extendedRange.inspect().StartRef(), extendedRange.inspect().EndRef());
7037 if (NS_FAILED(rv)) {
7038 NS_WARNING("Selection::SetBaseAndExtentInLimiter() failed");
7039 return Err(rv);
7043 rv = AlignContentsAtRanges(selectionRanges, aAlignType, aEditingHost);
7044 if (NS_FAILED(rv)) {
7045 NS_WARNING("HTMLEditor::AlignContentsAtSelection() failed");
7046 return Err(rv);
7048 rv = selectionRanges.ApplyTo(SelectionRef());
7049 if (NS_FAILED(rv)) {
7050 NS_WARNING("AutoRangeArray::ApplyTo() failed");
7051 return Err(rv);
7054 if (MOZ_UNLIKELY(IsSelectionRangeContainerNotContent())) {
7055 NS_WARNING("Mutation event listener might have changed the selection");
7056 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
7059 rv = MaybeInsertPaddingBRElementForEmptyLastLineAtSelection();
7060 if (NS_FAILED(rv)) {
7061 NS_WARNING(
7062 "HTMLEditor::MaybeInsertPaddingBRElementForEmptyLastLineAtSelection() "
7063 "failed");
7064 return Err(rv);
7066 return EditActionResult::HandledResult();
7069 nsresult HTMLEditor::AlignContentsAtRanges(AutoRangeArray& aRanges,
7070 const nsAString& aAlignType,
7071 const Element& aEditingHost) {
7072 MOZ_ASSERT(IsEditActionDataAvailable());
7073 MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable());
7074 MOZ_ASSERT(aRanges.IsInContent());
7076 if (NS_WARN_IF(!aRanges.SaveAndTrackRanges(*this))) {
7077 return NS_ERROR_FAILURE;
7080 EditorDOMPoint pointToPutCaret;
7082 // Convert the selection ranges into "promoted" selection ranges: This
7083 // basically just expands the range to include the immediate block parent,
7084 // and then further expands to include any ancestors whose children are all
7085 // in the range
7086 AutoTArray<OwningNonNull<nsIContent>, 64> arrayOfContents;
7088 AutoRangeArray extendedRanges(aRanges);
7089 extendedRanges.ExtendRangesToWrapLines(
7090 EditSubAction::eSetOrClearAlignment,
7091 BlockInlineCheck::UseHTMLDefaultStyle, aEditingHost);
7092 Result<EditorDOMPoint, nsresult> splitResult =
7093 extendedRanges
7094 .SplitTextAtEndBoundariesAndInlineAncestorsAtBothBoundaries(
7095 *this, BlockInlineCheck::UseHTMLDefaultStyle, aEditingHost);
7096 if (MOZ_UNLIKELY(splitResult.isErr())) {
7097 NS_WARNING(
7098 "AutoRangeArray::"
7099 "SplitTextAtEndBoundariesAndInlineAncestorsAtBothBoundaries() "
7100 "failed");
7101 return splitResult.unwrapErr();
7103 if (splitResult.inspect().IsSet()) {
7104 pointToPutCaret = splitResult.unwrap();
7106 nsresult rv = extendedRanges.CollectEditTargetNodes(
7107 *this, arrayOfContents, EditSubAction::eSetOrClearAlignment,
7108 AutoRangeArray::CollectNonEditableNodes::Yes);
7109 if (NS_FAILED(rv)) {
7110 NS_WARNING(
7111 "AutoRangeArray::CollectEditTargetNodes(EditSubAction::"
7112 "eSetOrClearAlignment, CollectNonEditableNodes::Yes) failed");
7113 return rv;
7117 Result<EditorDOMPoint, nsresult> splitAtBRElementsResult =
7118 MaybeSplitElementsAtEveryBRElement(arrayOfContents,
7119 EditSubAction::eSetOrClearAlignment);
7120 if (MOZ_UNLIKELY(splitAtBRElementsResult.isErr())) {
7121 NS_WARNING(
7122 "HTMLEditor::MaybeSplitElementsAtEveryBRElement(EditSubAction::"
7123 "eSetOrClearAlignment) failed");
7124 return splitAtBRElementsResult.inspectErr();
7126 if (splitAtBRElementsResult.inspect().IsSet()) {
7127 pointToPutCaret = splitAtBRElementsResult.unwrap();
7130 // If we don't have any nodes, or we have only a single br, then we are
7131 // creating an empty alignment div. We have to do some different things for
7132 // these.
7133 bool createEmptyDivElement = arrayOfContents.IsEmpty();
7134 if (arrayOfContents.Length() == 1) {
7135 OwningNonNull<nsIContent>& content = arrayOfContents[0];
7137 if (HTMLEditUtils::SupportsAlignAttr(content) &&
7138 HTMLEditUtils::IsBlockElement(content,
7139 BlockInlineCheck::UseHTMLDefaultStyle)) {
7140 // The node is a table element, an hr, a paragraph, a div or a section
7141 // header; in HTML 4, it can directly carry the ALIGN attribute and we
7142 // don't need to make a div! If we are in CSS mode, all the work is done
7143 // in SetBlockElementAlign().
7144 Result<EditorDOMPoint, nsresult> pointToPutCaretOrError =
7145 SetBlockElementAlign(MOZ_KnownLive(*content->AsElement()), aAlignType,
7146 EditTarget::OnlyDescendantsExceptTable);
7147 if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) {
7148 NS_WARNING("HTMLEditor::SetBlockElementAlign() failed");
7149 return pointToPutCaretOrError.unwrapErr();
7151 if (pointToPutCaretOrError.inspect().IsSet()) {
7152 pointToPutCaret = pointToPutCaretOrError.unwrap();
7156 if (content->IsHTMLElement(nsGkAtoms::br)) {
7157 // The special case createEmptyDivElement code (below) that consumes
7158 // `<br>` elements can cause tables to split if the start node of the
7159 // selection is not in a table cell or caption, for example parent is a
7160 // `<tr>`. Avoid this unnecessary splitting if possible by leaving
7161 // createEmptyDivElement false so that we fall through to the normal case
7162 // alignment code.
7164 // XXX: It seems a little error prone for the createEmptyDivElement
7165 // special case code to assume that the start node of the selection
7166 // is the parent of the single node in the arrayOfContents, as the
7167 // paragraph above points out. Do we rely on the selection start
7168 // node because of the fact that arrayOfContents can be empty? We
7169 // should probably revisit this issue. - kin
7171 const EditorDOMPoint firstRangeStartPoint =
7172 pointToPutCaret.IsSet()
7173 ? pointToPutCaret
7174 : aRanges.GetFirstRangeStartPoint<EditorDOMPoint>();
7175 if (NS_WARN_IF(!firstRangeStartPoint.IsSet())) {
7176 return NS_ERROR_FAILURE;
7178 nsINode* parent = firstRangeStartPoint.GetContainer();
7179 createEmptyDivElement = !HTMLEditUtils::IsAnyTableElement(parent) ||
7180 HTMLEditUtils::IsTableCellOrCaption(*parent);
7184 if (createEmptyDivElement) {
7185 if (MOZ_UNLIKELY(!pointToPutCaret.IsSet() && !aRanges.IsInContent())) {
7186 NS_WARNING("Mutation event listener might have changed the selection");
7187 return NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE;
7189 const EditorDOMPoint pointToInsertDivElement =
7190 pointToPutCaret.IsSet()
7191 ? pointToPutCaret
7192 : aRanges.GetFirstRangeStartPoint<EditorDOMPoint>();
7193 Result<CreateElementResult, nsresult> insertNewDivElementResult =
7194 InsertDivElementToAlignContents(pointToInsertDivElement, aAlignType,
7195 aEditingHost);
7196 if (insertNewDivElementResult.isErr()) {
7197 NS_WARNING("HTMLEditor::InsertDivElementToAlignContents() failed");
7198 return insertNewDivElementResult.unwrapErr();
7200 CreateElementResult unwrappedInsertNewDivElementResult =
7201 insertNewDivElementResult.unwrap();
7202 aRanges.ClearSavedRanges();
7203 EditorDOMPoint pointToPutCaret =
7204 unwrappedInsertNewDivElementResult.UnwrapCaretPoint();
7205 nsresult rv = aRanges.Collapse(pointToPutCaret);
7206 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "AutoRangeArray::Collapse() failed");
7207 return rv;
7210 Result<CreateElementResult, nsresult> maybeCreateDivElementResult =
7211 AlignNodesAndDescendants(arrayOfContents, aAlignType, aEditingHost);
7212 if (MOZ_UNLIKELY(maybeCreateDivElementResult.isErr())) {
7213 NS_WARNING("HTMLEditor::AlignNodesAndDescendants() failed");
7214 return maybeCreateDivElementResult.unwrapErr();
7216 maybeCreateDivElementResult.inspect().IgnoreCaretPointSuggestion();
7218 MOZ_ASSERT(aRanges.HasSavedRanges());
7219 aRanges.RestoreFromSavedRanges();
7220 // If restored range is collapsed outside the latest cased <div> element,
7221 // we should move caret into the <div>.
7222 if (maybeCreateDivElementResult.inspect().GetNewNode() &&
7223 aRanges.IsCollapsed() && !aRanges.Ranges().IsEmpty()) {
7224 const auto firstRangeStartRawPoint =
7225 aRanges.GetFirstRangeStartPoint<EditorRawDOMPoint>();
7226 if (MOZ_LIKELY(firstRangeStartRawPoint.IsSet())) {
7227 Result<EditorRawDOMPoint, nsresult> pointInNewDivOrError =
7228 HTMLEditUtils::ComputePointToPutCaretInElementIfOutside<
7229 EditorRawDOMPoint>(
7230 *maybeCreateDivElementResult.inspect().GetNewNode(),
7231 firstRangeStartRawPoint);
7232 if (MOZ_UNLIKELY(pointInNewDivOrError.isErr())) {
7233 NS_WARNING(
7234 "HTMLEditUtils::ComputePointToPutCaretInElementIfOutside() failed, "
7235 "but ignored");
7236 } else if (pointInNewDivOrError.inspect().IsSet()) {
7237 nsresult rv = aRanges.Collapse(pointInNewDivOrError.unwrap());
7238 if (NS_FAILED(rv)) {
7239 NS_WARNING("AutoRangeArray::Collapse() failed");
7240 return rv;
7245 return NS_OK;
7248 Result<CreateElementResult, nsresult>
7249 HTMLEditor::InsertDivElementToAlignContents(
7250 const EditorDOMPoint& aPointToInsert, const nsAString& aAlignType,
7251 const Element& aEditingHost) {
7252 MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable());
7253 MOZ_ASSERT(!IsSelectionRangeContainerNotContent());
7254 MOZ_ASSERT(aPointToInsert.IsSetAndValid());
7256 if (NS_WARN_IF(!aPointToInsert.IsSet())) {
7257 return Err(NS_ERROR_FAILURE);
7260 Result<CreateElementResult, nsresult> createNewDivElementResult =
7261 InsertElementWithSplittingAncestorsWithTransaction(
7262 *nsGkAtoms::div, aPointToInsert, BRElementNextToSplitPoint::Delete,
7263 aEditingHost);
7264 if (MOZ_UNLIKELY(createNewDivElementResult.isErr())) {
7265 NS_WARNING(
7266 "HTMLEditor::InsertElementWithSplittingAncestorsWithTransaction("
7267 "nsGkAtoms::div, BRElementNextToSplitPoint::Delete) failed");
7268 return createNewDivElementResult;
7270 CreateElementResult unwrappedCreateNewDivElementResult =
7271 createNewDivElementResult.unwrap();
7272 // We'll suggest start of the new <div>, so we don't need the suggested
7273 // position.
7274 unwrappedCreateNewDivElementResult.IgnoreCaretPointSuggestion();
7276 MOZ_ASSERT(unwrappedCreateNewDivElementResult.GetNewNode());
7277 RefPtr<Element> newDivElement =
7278 unwrappedCreateNewDivElementResult.UnwrapNewNode();
7279 // Set up the alignment on the div, using HTML or CSS
7280 Result<EditorDOMPoint, nsresult> pointToPutCaretOrError =
7281 SetBlockElementAlign(*newDivElement, aAlignType,
7282 EditTarget::OnlyDescendantsExceptTable);
7283 if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) {
7284 NS_WARNING(
7285 "HTMLEditor::SetBlockElementAlign(EditTarget::"
7286 "OnlyDescendantsExceptTable) failed");
7287 return pointToPutCaretOrError.propagateErr();
7289 // We don't need the new suggested position too.
7291 // Put in a padding <br> element for empty last line so that it won't get
7292 // deleted.
7294 Result<CreateElementResult, nsresult> insertPaddingBRElementResult =
7295 InsertPaddingBRElementForEmptyLastLineWithTransaction(
7296 EditorDOMPoint(newDivElement, 0u));
7297 if (MOZ_UNLIKELY(insertPaddingBRElementResult.isErr())) {
7298 NS_WARNING(
7299 "HTMLEditor::InsertPaddingBRElementForEmptyLastLineWithTransaction() "
7300 "failed");
7301 return insertPaddingBRElementResult;
7303 insertPaddingBRElementResult.inspect().IgnoreCaretPointSuggestion();
7306 return CreateElementResult(std::move(newDivElement),
7307 EditorDOMPoint(newDivElement, 0u));
7310 Result<CreateElementResult, nsresult> HTMLEditor::AlignNodesAndDescendants(
7311 nsTArray<OwningNonNull<nsIContent>>& aArrayOfContents,
7312 const nsAString& aAlignType, const Element& aEditingHost) {
7313 // Detect all the transitions in the array, where a transition means that
7314 // adjacent nodes in the array don't have the same parent.
7315 AutoTArray<bool, 64> transitionList;
7316 HTMLEditor::MakeTransitionList(aArrayOfContents, transitionList);
7318 RefPtr<Element> latestCreatedDivElement;
7319 EditorDOMPoint pointToPutCaret;
7321 // Okay, now go through all the nodes and give them an align attrib or put
7322 // them in a div, or whatever is appropriate. Woohoo!
7324 RefPtr<Element> createdDivElement;
7325 const bool useCSS = IsCSSEnabled();
7326 int32_t indexOfTransitionList = -1;
7327 for (OwningNonNull<nsIContent>& content : aArrayOfContents) {
7328 ++indexOfTransitionList;
7330 // Ignore all non-editable nodes. Leave them be.
7331 if (!EditorUtils::IsEditableContent(content, EditorType::HTML)) {
7332 continue;
7335 // The node is a table element, an hr, a paragraph, a div or a section
7336 // header; in HTML 4, it can directly carry the ALIGN attribute and we
7337 // don't need to nest it, just set the alignment. In CSS, assign the
7338 // corresponding CSS styles in SetBlockElementAlign().
7339 if (HTMLEditUtils::SupportsAlignAttr(content)) {
7340 Result<EditorDOMPoint, nsresult> pointToPutCaretOrError =
7341 SetBlockElementAlign(MOZ_KnownLive(*content->AsElement()), aAlignType,
7342 EditTarget::NodeAndDescendantsExceptTable);
7343 if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) {
7344 NS_WARNING(
7345 "HTMLEditor::SetBlockElementAlign(EditTarget::"
7346 "NodeAndDescendantsExceptTable) failed");
7347 return pointToPutCaretOrError.propagateErr();
7349 if (pointToPutCaretOrError.inspect().IsSet()) {
7350 pointToPutCaret = pointToPutCaretOrError.unwrap();
7352 // Clear out createdDivElement so that we don't put nodes after this one
7353 // into it
7354 createdDivElement = nullptr;
7355 continue;
7358 EditorDOMPoint atContent(content);
7359 if (NS_WARN_IF(!atContent.IsSet())) {
7360 continue;
7363 // Skip insignificant formatting text nodes to prevent unnecessary
7364 // structure splitting!
7365 if (content->IsText() &&
7366 ((HTMLEditUtils::IsAnyTableElement(atContent.GetContainer()) &&
7367 !HTMLEditUtils::IsTableCellOrCaption(*atContent.GetContainer())) ||
7368 HTMLEditUtils::IsAnyListElement(atContent.GetContainer()) ||
7369 HTMLEditUtils::IsEmptyNode(
7370 *content,
7371 {EmptyCheckOption::TreatSingleBRElementAsVisible,
7372 EmptyCheckOption::TreatNonEditableContentAsInvisible}))) {
7373 continue;
7376 // If it's a list item, or a list inside a list, forget any "current" div,
7377 // and instead put divs inside the appropriate block (td, li, etc.)
7378 if (HTMLEditUtils::IsListItem(content) ||
7379 HTMLEditUtils::IsAnyListElement(content)) {
7380 Element* listOrListItemElement = content->AsElement();
7382 AutoEditorDOMPointOffsetInvalidator lockChild(atContent);
7383 // MOZ_KnownLive(*listOrListItemElement): An element of aArrayOfContents
7384 // which is array of OwningNonNull.
7385 Result<EditorDOMPoint, nsresult> pointToPutCaretOrError =
7386 RemoveAlignFromDescendants(MOZ_KnownLive(*listOrListItemElement),
7387 aAlignType,
7388 EditTarget::OnlyDescendantsExceptTable);
7389 if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) {
7390 NS_WARNING(
7391 "HTMLEditor::RemoveAlignFromDescendants(EditTarget::"
7392 "OnlyDescendantsExceptTable) failed");
7393 return pointToPutCaretOrError.propagateErr();
7395 if (pointToPutCaretOrError.inspect().IsSet()) {
7396 pointToPutCaret = pointToPutCaretOrError.unwrap();
7399 if (NS_WARN_IF(!atContent.IsSetAndValid())) {
7400 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
7403 if (useCSS) {
7404 nsStyledElement* styledListOrListItemElement =
7405 nsStyledElement::FromNode(listOrListItemElement);
7406 if (styledListOrListItemElement &&
7407 EditorElementStyle::Align().IsCSSSettable(
7408 *styledListOrListItemElement)) {
7409 // MOZ_KnownLive(*styledListOrListItemElement): An element of
7410 // aArrayOfContents which is array of OwningNonNull.
7411 Result<size_t, nsresult> result =
7412 CSSEditUtils::SetCSSEquivalentToStyle(
7413 WithTransaction::Yes, *this,
7414 MOZ_KnownLive(*styledListOrListItemElement),
7415 EditorElementStyle::Align(), &aAlignType);
7416 if (MOZ_UNLIKELY(result.isErr())) {
7417 if (NS_WARN_IF(result.inspectErr() == NS_ERROR_EDITOR_DESTROYED)) {
7418 return result.propagateErr();
7420 NS_WARNING(
7421 "CSSEditUtils::SetCSSEquivalentToStyle(EditorElementStyle::"
7422 "Align()) failed, but ignored");
7425 createdDivElement = nullptr;
7426 continue;
7429 if (HTMLEditUtils::IsAnyListElement(atContent.GetContainer())) {
7430 // If we don't use CSS, add a content to list element: they have to
7431 // be inside another list, i.e., >= second level of nesting.
7432 // XXX AlignContentsInAllTableCellsAndListItems() handles only list
7433 // item elements and table cells. Is it intentional? Why don't
7434 // we need to align contents in other type blocks?
7435 // MOZ_KnownLive(*listOrListItemElement): An element of aArrayOfContents
7436 // which is array of OwningNonNull.
7437 Result<EditorDOMPoint, nsresult> pointToPutCaretOrError =
7438 AlignContentsInAllTableCellsAndListItems(
7439 MOZ_KnownLive(*listOrListItemElement), aAlignType);
7440 if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) {
7441 NS_WARNING(
7442 "HTMLEditor::AlignContentsInAllTableCellsAndListItems() failed");
7443 return pointToPutCaretOrError.propagateErr();
7445 if (pointToPutCaretOrError.inspect().IsSet()) {
7446 pointToPutCaret = pointToPutCaretOrError.unwrap();
7448 createdDivElement = nullptr;
7449 continue;
7452 // Clear out createdDivElement so that we don't put nodes after this one
7453 // into it
7456 // Need to make a div to put things in if we haven't already, or if this
7457 // node doesn't go in div we used earlier.
7458 if (!createdDivElement || transitionList[indexOfTransitionList]) {
7459 // First, check that our element can contain a div.
7460 if (!HTMLEditUtils::CanNodeContain(*atContent.GetContainer(),
7461 *nsGkAtoms::div)) {
7462 // XXX Why do we return "OK" here rather than returning error or
7463 // doing continue?
7464 return latestCreatedDivElement
7465 ? CreateElementResult(std::move(latestCreatedDivElement),
7466 std::move(pointToPutCaret))
7467 : CreateElementResult::NotHandled(
7468 std::move(pointToPutCaret));
7471 Result<CreateElementResult, nsresult> createNewDivElementResult =
7472 InsertElementWithSplittingAncestorsWithTransaction(
7473 *nsGkAtoms::div, atContent, BRElementNextToSplitPoint::Keep,
7474 aEditingHost);
7475 if (MOZ_UNLIKELY(createNewDivElementResult.isErr())) {
7476 NS_WARNING(
7477 "HTMLEditor::InsertElementWithSplittingAncestorsWithTransaction("
7478 "nsGkAtoms::div) failed");
7479 return createNewDivElementResult;
7481 CreateElementResult unwrappedCreateNewDivElementResult =
7482 createNewDivElementResult.unwrap();
7483 if (unwrappedCreateNewDivElementResult.HasCaretPointSuggestion()) {
7484 pointToPutCaret = unwrappedCreateNewDivElementResult.UnwrapCaretPoint();
7487 MOZ_ASSERT(unwrappedCreateNewDivElementResult.GetNewNode());
7488 createdDivElement = unwrappedCreateNewDivElementResult.UnwrapNewNode();
7489 // Set up the alignment on the div
7490 Result<EditorDOMPoint, nsresult> pointToPutCaretOrError =
7491 SetBlockElementAlign(*createdDivElement, aAlignType,
7492 EditTarget::OnlyDescendantsExceptTable);
7493 if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) {
7494 if (NS_WARN_IF(pointToPutCaretOrError.inspectErr() ==
7495 NS_ERROR_EDITOR_DESTROYED)) {
7496 return pointToPutCaretOrError.propagateErr();
7498 NS_WARNING(
7499 "HTMLEditor::SetBlockElementAlign(EditTarget::"
7500 "OnlyDescendantsExceptTable) failed, but ignored");
7501 } else if (pointToPutCaretOrError.inspect().IsSet()) {
7502 pointToPutCaret = pointToPutCaretOrError.unwrap();
7504 latestCreatedDivElement = createdDivElement;
7507 // Tuck the node into the end of the active div
7509 // MOZ_KnownLive because 'aArrayOfContents' is guaranteed to keep it alive.
7510 Result<MoveNodeResult, nsresult> moveNodeResult =
7511 MoveNodeToEndWithTransaction(MOZ_KnownLive(content),
7512 *createdDivElement);
7513 if (MOZ_UNLIKELY(moveNodeResult.isErr())) {
7514 NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed");
7515 return moveNodeResult.propagateErr();
7517 MoveNodeResult unwrappedMoveNodeResult = moveNodeResult.unwrap();
7518 if (unwrappedMoveNodeResult.HasCaretPointSuggestion()) {
7519 pointToPutCaret = unwrappedMoveNodeResult.UnwrapCaretPoint();
7523 return latestCreatedDivElement
7524 ? CreateElementResult(std::move(latestCreatedDivElement),
7525 std::move(pointToPutCaret))
7526 : CreateElementResult::NotHandled(std::move(pointToPutCaret));
7529 Result<EditorDOMPoint, nsresult>
7530 HTMLEditor::AlignContentsInAllTableCellsAndListItems(
7531 Element& aElement, const nsAString& aAlignType) {
7532 MOZ_ASSERT(IsEditActionDataAvailable());
7534 // Gather list of table cells or list items
7535 AutoTArray<OwningNonNull<Element>, 64> arrayOfTableCellsAndListItems;
7536 DOMIterator iter(aElement);
7537 iter.AppendNodesToArray(
7538 +[](nsINode& aNode, void*) -> bool {
7539 MOZ_ASSERT(Element::FromNode(&aNode));
7540 return HTMLEditUtils::IsTableCell(&aNode) ||
7541 HTMLEditUtils::IsListItem(&aNode);
7543 arrayOfTableCellsAndListItems);
7545 // Now that we have the list, align their contents as requested
7546 EditorDOMPoint pointToPutCaret;
7547 for (auto& tableCellOrListItemElement : arrayOfTableCellsAndListItems) {
7548 // MOZ_KnownLive because 'arrayOfTableCellsAndListItems' is guaranteed to
7549 // keep it alive.
7550 Result<EditorDOMPoint, nsresult> pointToPutCaretOrError =
7551 AlignBlockContentsWithDivElement(
7552 MOZ_KnownLive(tableCellOrListItemElement), aAlignType);
7553 if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) {
7554 NS_WARNING("HTMLEditor::AlignBlockContentsWithDivElement() failed");
7555 return pointToPutCaretOrError;
7557 if (pointToPutCaretOrError.inspect().IsSet()) {
7558 pointToPutCaret = pointToPutCaretOrError.unwrap();
7562 return pointToPutCaret;
7565 Result<EditorDOMPoint, nsresult> HTMLEditor::AlignBlockContentsWithDivElement(
7566 Element& aBlockElement, const nsAString& aAlignType) {
7567 MOZ_ASSERT(IsEditActionDataAvailable());
7569 // XXX I don't understand why we should NOT align non-editable children
7570 // with modifying EDITABLE `<div>` element.
7571 nsCOMPtr<nsIContent> firstEditableContent = HTMLEditUtils::GetFirstChild(
7572 aBlockElement, {WalkTreeOption::IgnoreNonEditableNode});
7573 if (!firstEditableContent) {
7574 // This block has no editable content, nothing to align.
7575 return EditorDOMPoint();
7578 // If there is only one editable content and it's a `<div>` element,
7579 // just set `align` attribute of it.
7580 nsCOMPtr<nsIContent> lastEditableContent = HTMLEditUtils::GetLastChild(
7581 aBlockElement, {WalkTreeOption::IgnoreNonEditableNode});
7582 if (firstEditableContent == lastEditableContent &&
7583 firstEditableContent->IsHTMLElement(nsGkAtoms::div)) {
7584 nsresult rv = SetAttributeOrEquivalent(
7585 MOZ_KnownLive(firstEditableContent->AsElement()), nsGkAtoms::align,
7586 aAlignType, false);
7587 if (NS_WARN_IF(Destroyed())) {
7588 NS_WARNING(
7589 "EditorBase::SetAttributeOrEquivalent(nsGkAtoms::align) caused "
7590 "destroying the editor");
7591 return Err(NS_ERROR_EDITOR_DESTROYED);
7593 if (NS_FAILED(rv)) {
7594 NS_WARNING(
7595 "EditorBase::SetAttributeOrEquivalent(nsGkAtoms::align) failed");
7596 return Err(rv);
7598 return EditorDOMPoint();
7601 // Otherwise, we need to insert a `<div>` element to set `align` attribute.
7602 // XXX Don't insert the new `<div>` element until we set `align` attribute
7603 // for avoiding running mutation event listeners.
7604 Result<CreateElementResult, nsresult> createNewDivElementResult =
7605 CreateAndInsertElement(
7606 WithTransaction::Yes, *nsGkAtoms::div,
7607 EditorDOMPoint(&aBlockElement, 0u),
7608 // MOZ_CAN_RUN_SCRIPT_BOUNDARY due to bug 1758868
7609 [&aAlignType](HTMLEditor& aHTMLEditor, Element& aDivElement,
7610 const EditorDOMPoint&) MOZ_CAN_RUN_SCRIPT_BOUNDARY {
7611 MOZ_ASSERT(!aDivElement.IsInComposedDoc());
7612 // If aDivElement has not been connected yet, we do not need
7613 // transaction of setting align attribute here.
7614 nsresult rv = aHTMLEditor.SetAttributeOrEquivalent(
7615 &aDivElement, nsGkAtoms::align, aAlignType, false);
7616 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
7617 "EditorBase::SetAttributeOrEquivalent("
7618 "nsGkAtoms::align, \"...\", false) failed");
7619 return rv;
7621 if (MOZ_UNLIKELY(createNewDivElementResult.isErr())) {
7622 NS_WARNING(
7623 "HTMLEditor::CreateAndInsertElement(WithTransaction::Yes, "
7624 "nsGkAtoms::div) failed");
7625 return createNewDivElementResult.propagateErr();
7627 CreateElementResult unwrappedCreateNewDivElementResult =
7628 createNewDivElementResult.unwrap();
7629 EditorDOMPoint pointToPutCaret =
7630 unwrappedCreateNewDivElementResult.UnwrapCaretPoint();
7631 RefPtr<Element> newDivElement =
7632 unwrappedCreateNewDivElementResult.UnwrapNewNode();
7633 MOZ_ASSERT(newDivElement);
7634 // XXX This is tricky and does not work with mutation event listeners.
7635 // But I'm not sure what we should do if new content is inserted.
7636 // Anyway, I don't think that we should move editable contents
7637 // over non-editable contents. Chrome does no do that.
7638 while (lastEditableContent && (lastEditableContent != newDivElement)) {
7639 Result<MoveNodeResult, nsresult> moveNodeResult = MoveNodeWithTransaction(
7640 *lastEditableContent, EditorDOMPoint(newDivElement, 0u));
7641 if (MOZ_UNLIKELY(moveNodeResult.isErr())) {
7642 NS_WARNING("HTMLEditor::MoveNodeWithTransaction() failed");
7643 return moveNodeResult.propagateErr();
7645 MoveNodeResult unwrappedMoveNodeResult = moveNodeResult.unwrap();
7646 if (unwrappedMoveNodeResult.HasCaretPointSuggestion()) {
7647 pointToPutCaret = unwrappedMoveNodeResult.UnwrapCaretPoint();
7649 lastEditableContent = HTMLEditUtils::GetLastChild(
7650 aBlockElement, {WalkTreeOption::IgnoreNonEditableNode});
7652 return pointToPutCaret;
7655 Result<EditorRawDOMRange, nsresult>
7656 HTMLEditor::GetRangeExtendedToHardLineEdgesForBlockEditAction(
7657 const nsRange* aRange, const Element& aEditingHost) const {
7658 MOZ_ASSERT(IsEditActionDataAvailable());
7660 // This tweaks selections to be more "natural".
7661 // Idea here is to adjust edges of selection ranges so that they do not cross
7662 // breaks or block boundaries unless something editable beyond that boundary
7663 // is also selected. This adjustment makes it much easier for the various
7664 // block operations to determine what nodes to act on.
7665 if (NS_WARN_IF(!aRange) || NS_WARN_IF(!aRange->IsPositioned())) {
7666 return Err(NS_ERROR_FAILURE);
7669 const EditorRawDOMPoint startPoint(aRange->StartRef());
7670 if (NS_WARN_IF(!startPoint.IsSet())) {
7671 return Err(NS_ERROR_FAILURE);
7673 const EditorRawDOMPoint endPoint(aRange->EndRef());
7674 if (NS_WARN_IF(!endPoint.IsSet())) {
7675 return Err(NS_ERROR_FAILURE);
7678 // adjusted values default to original values
7679 EditorRawDOMRange newRange(startPoint, endPoint);
7681 // Is there any intervening visible white-space? If so we can't push
7682 // selection past that, it would visibly change meaning of users selection.
7683 WSRunScanner wsScannerAtEnd(
7684 &aEditingHost, endPoint,
7685 // We should refer only the default style of HTML because we need to wrap
7686 // any elements with a specific HTML element. So we should not refer
7687 // actual style. For example, we want to reformat parent HTML block
7688 // element even if selected in a blocked phrase element or
7689 // non-HTMLelement.
7690 BlockInlineCheck::UseHTMLDefaultStyle);
7691 const WSScanResult scanResultAtEnd =
7692 wsScannerAtEnd.ScanPreviousVisibleNodeOrBlockBoundaryFrom(endPoint);
7693 if (scanResultAtEnd.Failed()) {
7694 NS_WARNING(
7695 "WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundaryFrom() failed");
7696 return Err(NS_ERROR_FAILURE);
7698 if (scanResultAtEnd.ReachedSomethingNonTextContent()) {
7699 // eThisBlock and eOtherBlock conveniently distinguish cases
7700 // of going "down" into a block and "up" out of a block.
7701 if (wsScannerAtEnd.StartsFromOtherBlockElement()) {
7702 // endpoint is just after the close of a block.
7703 if (nsIContent* child = HTMLEditUtils::GetLastLeafContent(
7704 *wsScannerAtEnd.StartReasonOtherBlockElementPtr(),
7705 {LeafNodeType::LeafNodeOrChildBlock},
7706 BlockInlineCheck::UseHTMLDefaultStyle)) {
7707 newRange.SetEnd(EditorRawDOMPoint::After(*child));
7709 // else block is empty - we can leave selection alone here, i think.
7710 } else if (wsScannerAtEnd.StartsFromCurrentBlockBoundary() ||
7711 wsScannerAtEnd.StartsFromInlineEditingHostBoundary()) {
7712 // endpoint is just after start of this block
7713 if (nsIContent* child = HTMLEditUtils::GetPreviousContent(
7714 endPoint, {WalkTreeOption::IgnoreNonEditableNode},
7715 BlockInlineCheck::UseHTMLDefaultStyle, &aEditingHost)) {
7716 newRange.SetEnd(EditorRawDOMPoint::After(*child));
7718 // else block is empty - we can leave selection alone here, i think.
7719 } else if (wsScannerAtEnd.StartsFromBRElement()) {
7720 // endpoint is just after break. lets adjust it to before it.
7721 newRange.SetEnd(
7722 EditorRawDOMPoint(wsScannerAtEnd.StartReasonBRElementPtr()));
7726 // Is there any intervening visible white-space? If so we can't push
7727 // selection past that, it would visibly change meaning of users selection.
7728 WSRunScanner wsScannerAtStart(&aEditingHost, startPoint,
7729 BlockInlineCheck::UseHTMLDefaultStyle);
7730 const WSScanResult scanResultAtStart =
7731 wsScannerAtStart.ScanInclusiveNextVisibleNodeOrBlockBoundaryFrom(
7732 startPoint);
7733 if (scanResultAtStart.Failed()) {
7734 NS_WARNING("WSRunScanner::ScanNextVisibleNodeOrBlockBoundaryFrom() failed");
7735 return Err(NS_ERROR_FAILURE);
7737 if (scanResultAtStart.ReachedSomethingNonTextContent()) {
7738 // eThisBlock and eOtherBlock conveniently distinguish cases
7739 // of going "down" into a block and "up" out of a block.
7740 if (wsScannerAtStart.EndsByOtherBlockElement()) {
7741 // startpoint is just before the start of a block.
7742 if (nsIContent* child = HTMLEditUtils::GetFirstLeafContent(
7743 *wsScannerAtStart.EndReasonOtherBlockElementPtr(),
7744 {LeafNodeType::LeafNodeOrChildBlock},
7745 BlockInlineCheck::UseHTMLDefaultStyle)) {
7746 newRange.SetStart(EditorRawDOMPoint(child));
7748 // else block is empty - we can leave selection alone here, i think.
7749 } else if (wsScannerAtStart.EndsByCurrentBlockBoundary() ||
7750 wsScannerAtStart.EndsByInlineEditingHostBoundary()) {
7751 // startpoint is just before end of this block
7752 if (nsIContent* child = HTMLEditUtils::GetNextContent(
7753 startPoint, {WalkTreeOption::IgnoreNonEditableNode},
7754 BlockInlineCheck::UseHTMLDefaultStyle, &aEditingHost)) {
7755 newRange.SetStart(EditorRawDOMPoint(child));
7757 // else block is empty - we can leave selection alone here, i think.
7758 } else if (wsScannerAtStart.EndsByBRElement()) {
7759 // startpoint is just before a break. lets adjust it to after it.
7760 newRange.SetStart(
7761 EditorRawDOMPoint::After(*wsScannerAtStart.EndReasonBRElementPtr()));
7765 // There is a demented possibility we have to check for. We might have a very
7766 // strange selection that is not collapsed and yet does not contain any
7767 // editable content, and satisfies some of the above conditions that cause
7768 // tweaking. In this case we don't want to tweak the selection into a block
7769 // it was never in, etc. There are a variety of strategies one might use to
7770 // try to detect these cases, but I think the most straightforward is to see
7771 // if the adjusted locations "cross" the old values: i.e., new end before old
7772 // start, or new start after old end. If so then just leave things alone.
7774 Maybe<int32_t> comp = nsContentUtils::ComparePoints(
7775 startPoint.ToRawRangeBoundary(), newRange.EndRef().ToRawRangeBoundary());
7777 if (NS_WARN_IF(!comp)) {
7778 return Err(NS_ERROR_FAILURE);
7781 if (*comp == 1) {
7782 return EditorRawDOMRange(); // New end before old start.
7785 comp = nsContentUtils::ComparePoints(newRange.StartRef().ToRawRangeBoundary(),
7786 endPoint.ToRawRangeBoundary());
7788 if (NS_WARN_IF(!comp)) {
7789 return Err(NS_ERROR_FAILURE);
7792 if (*comp == 1) {
7793 return EditorRawDOMRange(); // New start after old end.
7796 return newRange;
7799 template <typename EditorDOMRangeType>
7800 already_AddRefed<nsRange> HTMLEditor::CreateRangeIncludingAdjuscentWhiteSpaces(
7801 const EditorDOMRangeType& aRange) {
7802 MOZ_DIAGNOSTIC_ASSERT(aRange.IsPositioned());
7803 return CreateRangeIncludingAdjuscentWhiteSpaces(aRange.StartRef(),
7804 aRange.EndRef());
7807 template <typename EditorDOMPointType1, typename EditorDOMPointType2>
7808 already_AddRefed<nsRange> HTMLEditor::CreateRangeIncludingAdjuscentWhiteSpaces(
7809 const EditorDOMPointType1& aStartPoint,
7810 const EditorDOMPointType2& aEndPoint) {
7811 MOZ_DIAGNOSTIC_ASSERT(!aStartPoint.IsInNativeAnonymousSubtree());
7812 MOZ_DIAGNOSTIC_ASSERT(!aEndPoint.IsInNativeAnonymousSubtree());
7814 if (!aStartPoint.IsInContentNode() || !aEndPoint.IsInContentNode()) {
7815 NS_WARNING_ASSERTION(aStartPoint.IsSet(), "aStartPoint was not set");
7816 NS_WARNING_ASSERTION(aEndPoint.IsSet(), "aEndPoint was not set");
7817 return nullptr;
7820 const Element* const editingHost = ComputeEditingHost();
7821 if (NS_WARN_IF(!editingHost)) {
7822 return nullptr;
7825 EditorDOMPoint startPoint = aStartPoint.template To<EditorDOMPoint>();
7826 EditorDOMPoint endPoint = aEndPoint.template To<EditorDOMPoint>();
7827 AutoRangeArray::UpdatePointsToSelectAllChildrenIfCollapsedInEmptyBlockElement(
7828 startPoint, endPoint, *editingHost);
7830 if (NS_WARN_IF(!startPoint.IsInContentNode()) ||
7831 NS_WARN_IF(!endPoint.IsInContentNode())) {
7832 NS_WARNING(
7833 "AutoRangeArray::"
7834 "UpdatePointsToSelectAllChildrenIfCollapsedInEmptyBlockElement() "
7835 "failed");
7836 return nullptr;
7839 // For text actions, we want to look backwards (or forwards, as
7840 // appropriate) for additional white-space or nbsp's. We may have to act
7841 // on these later even though they are outside of the initial selection.
7842 // Even if they are in another node!
7843 // XXX Those scanners do not treat siblings of the text nodes. Perhaps,
7844 // we should use `WSRunScanner::GetFirstASCIIWhiteSpacePointCollapsedTo()`
7845 // and `WSRunScanner::GetEndOfCollapsibleASCIIWhiteSpaces()` instead.
7846 if (startPoint.IsInTextNode()) {
7847 while (!startPoint.IsStartOfContainer()) {
7848 if (!startPoint.IsPreviousCharASCIISpaceOrNBSP()) {
7849 break;
7851 MOZ_ALWAYS_TRUE(startPoint.RewindOffset());
7854 if (!startPoint.GetChildOrContainerIfDataNode() ||
7855 !startPoint.GetChildOrContainerIfDataNode()->IsInclusiveDescendantOf(
7856 editingHost)) {
7857 return nullptr;
7859 if (endPoint.IsInTextNode()) {
7860 while (!endPoint.IsEndOfContainer()) {
7861 if (!endPoint.IsCharASCIISpaceOrNBSP()) {
7862 break;
7864 MOZ_ALWAYS_TRUE(endPoint.AdvanceOffset());
7867 EditorDOMPoint lastRawPoint(endPoint);
7868 if (!lastRawPoint.IsStartOfContainer()) {
7869 lastRawPoint.RewindOffset();
7871 if (!lastRawPoint.GetChildOrContainerIfDataNode() ||
7872 !lastRawPoint.GetChildOrContainerIfDataNode()->IsInclusiveDescendantOf(
7873 editingHost)) {
7874 return nullptr;
7877 RefPtr<nsRange> range =
7878 nsRange::Create(startPoint.ToRawRangeBoundary(),
7879 endPoint.ToRawRangeBoundary(), IgnoreErrors());
7880 NS_WARNING_ASSERTION(range, "nsRange::Create() failed");
7881 return range.forget();
7884 Result<EditorDOMPoint, nsresult> HTMLEditor::MaybeSplitElementsAtEveryBRElement(
7885 nsTArray<OwningNonNull<nsIContent>>& aArrayOfContents,
7886 EditSubAction aEditSubAction) {
7887 // Post-process the list to break up inline containers that contain br's, but
7888 // only for operations that might care, like making lists or paragraphs
7889 switch (aEditSubAction) {
7890 case EditSubAction::eCreateOrRemoveBlock:
7891 case EditSubAction::eFormatBlockForHTMLCommand:
7892 case EditSubAction::eMergeBlockContents:
7893 case EditSubAction::eCreateOrChangeList:
7894 case EditSubAction::eSetOrClearAlignment:
7895 case EditSubAction::eSetPositionToAbsolute:
7896 case EditSubAction::eIndent:
7897 case EditSubAction::eOutdent: {
7898 EditorDOMPoint pointToPutCaret;
7899 for (size_t index : Reversed(IntegerRange(aArrayOfContents.Length()))) {
7900 OwningNonNull<nsIContent>& content = aArrayOfContents[index];
7901 if (HTMLEditUtils::IsInlineContent(
7902 content, BlockInlineCheck::UseHTMLDefaultStyle) &&
7903 HTMLEditUtils::IsContainerNode(content) && !content->IsText()) {
7904 AutoTArray<OwningNonNull<nsIContent>, 24> arrayOfInlineContents;
7905 // MOZ_KnownLive because 'aArrayOfContents' is guaranteed to keep it
7906 // alive.
7907 Result<EditorDOMPoint, nsresult> splitResult =
7908 SplitElementsAtEveryBRElement(MOZ_KnownLive(content),
7909 arrayOfInlineContents);
7910 if (splitResult.isErr()) {
7911 NS_WARNING("HTMLEditor::SplitElementsAtEveryBRElement() failed");
7912 return splitResult;
7914 if (splitResult.inspect().IsSet()) {
7915 pointToPutCaret = splitResult.unwrap();
7917 // Put these nodes in aArrayOfContents, replacing the current node
7918 aArrayOfContents.RemoveElementAt(index);
7919 aArrayOfContents.InsertElementsAt(index, arrayOfInlineContents);
7922 return pointToPutCaret;
7924 default:
7925 return EditorDOMPoint();
7929 Result<EditorDOMPoint, nsresult>
7930 HTMLEditor::SplitInlineAncestorsAtRangeBoundaries(
7931 RangeItem& aRangeItem, BlockInlineCheck aBlockInlineCheck,
7932 const Element& aEditingHost,
7933 const nsIContent* aAncestorLimiter /* = nullptr */) {
7934 MOZ_ASSERT(IsEditActionDataAvailable());
7936 EditorDOMPoint pointToPutCaret;
7937 if (!aRangeItem.Collapsed() && aRangeItem.mEndContainer &&
7938 aRangeItem.mEndContainer->IsContent()) {
7939 nsCOMPtr<nsIContent> mostAncestorInlineContentAtEnd =
7940 HTMLEditUtils::GetMostDistantAncestorInlineElement(
7941 *aRangeItem.mEndContainer->AsContent(), aBlockInlineCheck,
7942 &aEditingHost, aAncestorLimiter);
7944 if (mostAncestorInlineContentAtEnd) {
7945 Result<SplitNodeResult, nsresult> splitEndInlineResult =
7946 SplitNodeDeepWithTransaction(
7947 *mostAncestorInlineContentAtEnd, aRangeItem.EndPoint(),
7948 SplitAtEdges::eDoNotCreateEmptyContainer);
7949 if (MOZ_UNLIKELY(splitEndInlineResult.isErr())) {
7950 NS_WARNING(
7951 "HTMLEditor::SplitNodeDeepWithTransaction(SplitAtEdges::"
7952 "eDoNotCreateEmptyContainer) failed");
7953 return splitEndInlineResult.propagateErr();
7955 SplitNodeResult unwrappedSplitEndInlineResult =
7956 splitEndInlineResult.unwrap();
7957 unwrappedSplitEndInlineResult.MoveCaretPointTo(
7958 pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
7959 if (pointToPutCaret.IsInContentNode() &&
7960 MOZ_UNLIKELY(
7961 &aEditingHost !=
7962 ComputeEditingHost(*pointToPutCaret.ContainerAs<nsIContent>()))) {
7963 NS_WARNING(
7964 "HTMLEditor::SplitNodeDeepWithTransaction(SplitAtEdges::"
7965 "eDoNotCreateEmptyContainer) caused changing editing host");
7966 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
7968 const auto splitPointAtEnd =
7969 unwrappedSplitEndInlineResult.AtSplitPoint<EditorRawDOMPoint>();
7970 if (MOZ_UNLIKELY(!splitPointAtEnd.IsSet())) {
7971 NS_WARNING(
7972 "HTMLEditor::SplitNodeDeepWithTransaction(SplitAtEdges::"
7973 "eDoNotCreateEmptyContainer) didn't return split point");
7974 return Err(NS_ERROR_FAILURE);
7976 aRangeItem.mEndContainer = splitPointAtEnd.GetContainer();
7977 aRangeItem.mEndOffset = splitPointAtEnd.Offset();
7981 if (!aRangeItem.mStartContainer || !aRangeItem.mStartContainer->IsContent()) {
7982 return pointToPutCaret;
7985 nsCOMPtr<nsIContent> mostAncestorInlineContentAtStart =
7986 HTMLEditUtils::GetMostDistantAncestorInlineElement(
7987 *aRangeItem.mStartContainer->AsContent(), aBlockInlineCheck,
7988 &aEditingHost, aAncestorLimiter);
7990 if (mostAncestorInlineContentAtStart) {
7991 Result<SplitNodeResult, nsresult> splitStartInlineResult =
7992 SplitNodeDeepWithTransaction(*mostAncestorInlineContentAtStart,
7993 aRangeItem.StartPoint(),
7994 SplitAtEdges::eDoNotCreateEmptyContainer);
7995 if (MOZ_UNLIKELY(splitStartInlineResult.isErr())) {
7996 NS_WARNING(
7997 "HTMLEditor::SplitNodeDeepWithTransaction(SplitAtEdges::"
7998 "eDoNotCreateEmptyContainer) failed");
7999 return splitStartInlineResult.propagateErr();
8001 SplitNodeResult unwrappedSplitStartInlineResult =
8002 splitStartInlineResult.unwrap();
8003 // XXX Why don't we check editing host like above??
8004 unwrappedSplitStartInlineResult.MoveCaretPointTo(
8005 pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
8006 // XXX If we split only here because of collapsed range, we're modifying
8007 // only start point of aRangeItem. Shouldn't we modify end point here
8008 // if it's collapsed?
8009 const auto splitPointAtStart =
8010 unwrappedSplitStartInlineResult.AtSplitPoint<EditorRawDOMPoint>();
8011 if (MOZ_UNLIKELY(!splitPointAtStart.IsSet())) {
8012 NS_WARNING(
8013 "HTMLEditor::SplitNodeDeepWithTransaction(SplitAtEdges::"
8014 "eDoNotCreateEmptyContainer) didn't return split point");
8015 return Err(NS_ERROR_FAILURE);
8017 aRangeItem.mStartContainer = splitPointAtStart.GetContainer();
8018 aRangeItem.mStartOffset = splitPointAtStart.Offset();
8021 return pointToPutCaret;
8024 Result<EditorDOMPoint, nsresult> HTMLEditor::SplitElementsAtEveryBRElement(
8025 nsIContent& aMostAncestorToBeSplit,
8026 nsTArray<OwningNonNull<nsIContent>>& aOutArrayOfContents) {
8027 MOZ_ASSERT(IsEditActionDataAvailable());
8029 // First build up a list of all the break nodes inside the inline container.
8030 AutoTArray<OwningNonNull<HTMLBRElement>, 24> arrayOfBRElements;
8031 DOMIterator iter(aMostAncestorToBeSplit);
8032 iter.AppendAllNodesToArray(arrayOfBRElements);
8034 // If there aren't any breaks, just put inNode itself in the array
8035 if (arrayOfBRElements.IsEmpty()) {
8036 aOutArrayOfContents.AppendElement(aMostAncestorToBeSplit);
8037 return EditorDOMPoint();
8040 // Else we need to bust up aMostAncestorToBeSplit along all the breaks
8041 nsCOMPtr<nsIContent> nextContent = &aMostAncestorToBeSplit;
8042 EditorDOMPoint pointToPutCaret;
8043 for (OwningNonNull<HTMLBRElement>& brElement : arrayOfBRElements) {
8044 EditorDOMPoint atBRNode(brElement);
8045 if (NS_WARN_IF(!atBRNode.IsSet())) {
8046 return Err(NS_ERROR_FAILURE);
8048 Result<SplitNodeResult, nsresult> splitNodeResult =
8049 SplitNodeDeepWithTransaction(
8050 *nextContent, atBRNode, SplitAtEdges::eAllowToCreateEmptyContainer);
8051 if (MOZ_UNLIKELY(splitNodeResult.isErr())) {
8052 NS_WARNING("HTMLEditor::SplitNodeDeepWithTransaction() failed");
8053 return splitNodeResult.propagateErr();
8055 SplitNodeResult unwrappedSplitNodeResult = splitNodeResult.unwrap();
8056 unwrappedSplitNodeResult.MoveCaretPointTo(
8057 pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
8058 // Put previous node at the split point.
8059 if (nsIContent* previousContent =
8060 unwrappedSplitNodeResult.GetPreviousContent()) {
8061 // Might not be a left node. A break might have been at the very
8062 // beginning of inline container, in which case
8063 // SplitNodeDeepWithTransaction() would not actually split anything.
8064 aOutArrayOfContents.AppendElement(*previousContent);
8067 // Move break outside of container and also put in node list
8068 // MOZ_KnownLive because 'arrayOfBRElements' is guaranteed to keep it alive.
8069 Result<MoveNodeResult, nsresult> moveBRElementResult =
8070 MoveNodeWithTransaction(
8071 MOZ_KnownLive(brElement),
8072 unwrappedSplitNodeResult.AtNextContent<EditorDOMPoint>());
8073 if (MOZ_UNLIKELY(moveBRElementResult.isErr())) {
8074 NS_WARNING("HTMLEditor::MoveNodeWithTransaction() failed");
8075 return moveBRElementResult.propagateErr();
8077 MoveNodeResult unwrappedMoveBRElementResult = moveBRElementResult.unwrap();
8078 unwrappedMoveBRElementResult.MoveCaretPointTo(
8079 pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
8080 aOutArrayOfContents.AppendElement(brElement);
8082 nextContent = unwrappedSplitNodeResult.GetNextContent();
8085 // Now tack on remaining next node.
8086 aOutArrayOfContents.AppendElement(*nextContent);
8088 return pointToPutCaret;
8091 // static
8092 void HTMLEditor::MakeTransitionList(
8093 const nsTArray<OwningNonNull<nsIContent>>& aArrayOfContents,
8094 nsTArray<bool>& aTransitionArray) {
8095 nsINode* prevParent = nullptr;
8096 aTransitionArray.EnsureLengthAtLeast(aArrayOfContents.Length());
8097 for (uint32_t i = 0; i < aArrayOfContents.Length(); i++) {
8098 aTransitionArray[i] = aArrayOfContents[i]->GetParentNode() != prevParent;
8099 prevParent = aArrayOfContents[i]->GetParentNode();
8103 Result<InsertParagraphResult, nsresult>
8104 HTMLEditor::HandleInsertParagraphInHeadingElement(
8105 Element& aHeadingElement, const EditorDOMPoint& aPointToSplit) {
8106 MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable());
8108 auto splitHeadingResult =
8109 [this, &aPointToSplit, &aHeadingElement]()
8110 MOZ_CAN_RUN_SCRIPT -> Result<SplitNodeResult, nsresult> {
8111 // Normalize collapsible white-spaces around the split point to keep
8112 // them visible after the split. Note that this does not touch
8113 // selection because of using AutoTransactionsConserveSelection in
8114 // WhiteSpaceVisibilityKeeper::ReplaceTextAndRemoveEmptyTextNodes().
8115 Result<EditorDOMPoint, nsresult> preparationResult =
8116 WhiteSpaceVisibilityKeeper::PrepareToSplitBlockElement(
8117 *this, aPointToSplit, aHeadingElement);
8118 if (MOZ_UNLIKELY(preparationResult.isErr())) {
8119 NS_WARNING(
8120 "WhiteSpaceVisibilityKeeper::PrepareToSplitBlockElement() "
8121 "failed");
8122 return preparationResult.propagateErr();
8124 EditorDOMPoint pointToSplit = preparationResult.unwrap();
8125 MOZ_ASSERT(pointToSplit.IsInContentNode());
8127 // Split the header
8128 Result<SplitNodeResult, nsresult> splitResult =
8129 SplitNodeDeepWithTransaction(
8130 aHeadingElement, pointToSplit,
8131 SplitAtEdges::eAllowToCreateEmptyContainer);
8132 NS_WARNING_ASSERTION(
8133 splitResult.isOk(),
8134 "HTMLEditor::SplitNodeDeepWithTransaction(aHeadingElement, "
8135 "SplitAtEdges::eAllowToCreateEmptyContainer) failed");
8136 return splitResult;
8137 }();
8138 if (MOZ_UNLIKELY(splitHeadingResult.isErr())) {
8139 NS_WARNING("Failed to splitting aHeadingElement");
8140 return splitHeadingResult.propagateErr();
8142 SplitNodeResult unwrappedSplitHeadingResult = splitHeadingResult.unwrap();
8143 unwrappedSplitHeadingResult.IgnoreCaretPointSuggestion();
8144 if (MOZ_UNLIKELY(!unwrappedSplitHeadingResult.DidSplit())) {
8145 NS_WARNING(
8146 "HTMLEditor::SplitNodeDeepWithTransaction(SplitAtEdges::"
8147 "eAllowToCreateEmptyContainer) didn't split aHeadingElement");
8148 return Err(NS_ERROR_FAILURE);
8151 // If the left heading element is empty, put a padding <br> element for empty
8152 // last line into it.
8153 // FYI: leftHeadingElement is grabbed by unwrappedSplitHeadingResult so that
8154 // it's safe to access anytime.
8155 auto* const leftHeadingElement =
8156 unwrappedSplitHeadingResult.GetPreviousContentAs<Element>();
8157 MOZ_ASSERT(leftHeadingElement,
8158 "SplitNodeResult::GetPreviousContent() should return something if "
8159 "DidSplit() returns true");
8160 MOZ_DIAGNOSTIC_ASSERT(HTMLEditUtils::IsHeader(*leftHeadingElement));
8161 if (HTMLEditUtils::IsEmptyNode(
8162 *leftHeadingElement,
8163 {EmptyCheckOption::TreatSingleBRElementAsVisible,
8164 EmptyCheckOption::TreatNonEditableContentAsInvisible})) {
8165 Result<CreateElementResult, nsresult> insertPaddingBRElementResult =
8166 InsertPaddingBRElementForEmptyLastLineWithTransaction(
8167 EditorDOMPoint(leftHeadingElement, 0u));
8168 if (MOZ_UNLIKELY(insertPaddingBRElementResult.isErr())) {
8169 NS_WARNING(
8170 "HTMLEditor::InsertPaddingBRElementForEmptyLastLineWithTransaction("
8171 ") failed");
8172 return insertPaddingBRElementResult.propagateErr();
8174 insertPaddingBRElementResult.inspect().IgnoreCaretPointSuggestion();
8177 // Put caret at start of the right head element if it's not empty.
8178 auto* const rightHeadingElement =
8179 unwrappedSplitHeadingResult.GetNextContentAs<Element>();
8180 MOZ_ASSERT(rightHeadingElement,
8181 "SplitNodeResult::GetNextContent() should return something if "
8182 "DidSplit() returns true");
8183 if (!HTMLEditUtils::IsEmptyBlockElement(
8184 *rightHeadingElement,
8185 {EmptyCheckOption::TreatNonEditableContentAsInvisible},
8186 BlockInlineCheck::UseComputedDisplayOutsideStyle)) {
8187 return InsertParagraphResult(rightHeadingElement,
8188 EditorDOMPoint(rightHeadingElement, 0u));
8191 // If the right heading element is empty, delete it.
8192 // TODO: If we know the new heading element becomes empty, we stop spliting
8193 // the heading element.
8194 // MOZ_KnownLive(rightHeadingElement) because it's grabbed by
8195 // unwrappedSplitHeadingResult.
8196 nsresult rv = DeleteNodeWithTransaction(MOZ_KnownLive(*rightHeadingElement));
8197 if (NS_FAILED(rv)) {
8198 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
8199 return Err(rv);
8202 // Layout tells the caret to blink in a weird place if we don't place a
8203 // break after the header.
8204 // XXX This block is dead code unless the removed right heading element is
8205 // reconnected by a mutation event listener. This is a regression of
8206 // bug 1405751:
8207 // https://searchfox.org/mozilla-central/diff/879f3317d1331818718e18776caa47be7f426a22/editor/libeditor/HTMLEditRules.cpp#6389
8208 // However, the traditional behavior is different from the other browsers.
8209 // Chrome creates new paragraph in this case. Therefore, we should just
8210 // drop this block in a follow up bug.
8211 if (rightHeadingElement->GetNextSibling()) {
8212 // XXX Ignoring non-editable <br> element here is odd because non-editable
8213 // <br> elements also work as <br> from point of view of layout.
8214 nsIContent* nextEditableSibling =
8215 HTMLEditUtils::GetNextSibling(*rightHeadingElement->GetNextSibling(),
8216 {WalkTreeOption::IgnoreNonEditableNode});
8217 if (nextEditableSibling &&
8218 nextEditableSibling->IsHTMLElement(nsGkAtoms::br)) {
8219 auto afterEditableBRElement = EditorDOMPoint::After(*nextEditableSibling);
8220 if (NS_WARN_IF(!afterEditableBRElement.IsSet())) {
8221 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
8223 // Put caret at the <br> element.
8224 return InsertParagraphResult::NotHandled(
8225 std::move(afterEditableBRElement));
8229 if (MOZ_UNLIKELY(!leftHeadingElement->IsInComposedDoc())) {
8230 NS_WARNING("The left heading element was unexpectedly removed");
8231 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
8234 TopLevelEditSubActionDataRef().mCachedPendingStyles->Clear();
8235 mPendingStylesToApplyToNewContent->ClearAllStyles();
8237 // Create a paragraph if the right heading element is not followed by an
8238 // editable <br> element.
8239 nsStaticAtom& newParagraphTagName =
8240 &DefaultParagraphSeparatorTagName() == nsGkAtoms::br
8241 ? *nsGkAtoms::p
8242 : DefaultParagraphSeparatorTagName();
8243 // We want a wrapper element even if we separate with a <br>.
8244 // MOZ_KnownLive(newParagraphTagName) because it's available until shutdown.
8245 Result<CreateElementResult, nsresult> createNewParagraphElementResult =
8246 CreateAndInsertElement(WithTransaction::Yes,
8247 MOZ_KnownLive(newParagraphTagName),
8248 EditorDOMPoint::After(*leftHeadingElement),
8249 HTMLEditor::InsertNewBRElement);
8250 if (MOZ_UNLIKELY(createNewParagraphElementResult.isErr())) {
8251 NS_WARNING(
8252 "HTMLEditor::CreateAndInsertElement(WithTransaction::Yes) failed");
8253 return createNewParagraphElementResult.propagateErr();
8255 CreateElementResult unwrappedCreateNewParagraphElementResult =
8256 createNewParagraphElementResult.unwrap();
8257 // Put caret at the <br> element in the following paragraph.
8258 unwrappedCreateNewParagraphElementResult.IgnoreCaretPointSuggestion();
8259 MOZ_ASSERT(unwrappedCreateNewParagraphElementResult.GetNewNode());
8260 EditorDOMPoint pointToPutCaret(
8261 unwrappedCreateNewParagraphElementResult.GetNewNode(), 0u);
8262 return InsertParagraphResult(
8263 unwrappedCreateNewParagraphElementResult.UnwrapNewNode(),
8264 std::move(pointToPutCaret));
8267 Result<SplitNodeResult, nsresult> HTMLEditor::HandleInsertParagraphInParagraph(
8268 Element& aParentDivOrP, const EditorDOMPoint& aCandidatePointToSplit,
8269 const Element& aEditingHost) {
8270 MOZ_ASSERT(IsEditActionDataAvailable());
8271 MOZ_ASSERT(aCandidatePointToSplit.IsSetAndValid());
8273 // First, get a better split point to avoid to create a new empty link in the
8274 // right paragraph.
8275 EditorDOMPoint pointToSplit = [&]() {
8276 // We shouldn't create new anchor element which has non-empty href unless
8277 // splitting middle of it because we assume that users don't want to create
8278 // *same* anchor element across two or more paragraphs in most cases.
8279 // So, adjust selection start if it's edge of anchor element(s).
8280 // XXX We don't support white-space collapsing in these cases since it needs
8281 // some additional work with WhiteSpaceVisibilityKeeper but it's not
8282 // usual case. E.g., |<a href="foo"><b>foo []</b> </a>|
8283 if (aCandidatePointToSplit.IsStartOfContainer()) {
8284 EditorDOMPoint candidatePoint(aCandidatePointToSplit);
8285 for (nsIContent* container =
8286 aCandidatePointToSplit.GetContainerAs<nsIContent>();
8287 container && container != &aParentDivOrP;
8288 container = container->GetParent()) {
8289 if (HTMLEditUtils::IsLink(container)) {
8290 // Found link should be only in right node. So, we shouldn't split
8291 // it.
8292 candidatePoint.Set(container);
8293 // Even if we found an anchor element, don't break because DOM API
8294 // allows to nest anchor elements.
8296 // If the container is middle of its parent, stop adjusting split point.
8297 if (container->GetPreviousSibling()) {
8298 // XXX Should we check if previous sibling is visible content?
8299 // E.g., should we ignore comment node, invisible <br> element?
8300 break;
8303 return candidatePoint;
8306 // We also need to check if selection is at invisible <br> element at end
8307 // of an <a href="foo"> element because editor inserts a <br> element when
8308 // user types Enter key after a white-space which is at middle of
8309 // <a href="foo"> element and when setting selection at end of the element,
8310 // selection becomes referring the <br> element. We may need to change this
8311 // behavior later if it'd be standardized.
8312 if (aCandidatePointToSplit.IsEndOfContainer() ||
8313 aCandidatePointToSplit.IsBRElementAtEndOfContainer()) {
8314 // If there are 2 <br> elements, the first <br> element is visible. E.g.,
8315 // |<a href="foo"><b>boo[]<br></b><br></a>|, we should split the <a>
8316 // element. Otherwise, E.g., |<a href="foo"><b>boo[]<br></b></a>|,
8317 // we should not split the <a> element and ignore inline elements in it.
8318 bool foundBRElement =
8319 aCandidatePointToSplit.IsBRElementAtEndOfContainer();
8320 EditorDOMPoint candidatePoint(aCandidatePointToSplit);
8321 for (nsIContent* container =
8322 aCandidatePointToSplit.GetContainerAs<nsIContent>();
8323 container && container != &aParentDivOrP;
8324 container = container->GetParent()) {
8325 if (HTMLEditUtils::IsLink(container)) {
8326 // Found link should be only in left node. So, we shouldn't split it.
8327 candidatePoint.SetAfter(container);
8328 // Even if we found an anchor element, don't break because DOM API
8329 // allows to nest anchor elements.
8331 // If the container is middle of its parent, stop adjusting split point.
8332 if (nsIContent* nextSibling = container->GetNextSibling()) {
8333 if (foundBRElement) {
8334 // If we've already found a <br> element, we assume found node is
8335 // visible <br> or something other node.
8336 // XXX Should we check if non-text data node like comment?
8337 break;
8340 // XXX Should we check if non-text data node like comment?
8341 if (!nextSibling->IsHTMLElement(nsGkAtoms::br)) {
8342 break;
8344 foundBRElement = true;
8347 return candidatePoint;
8349 return aCandidatePointToSplit;
8350 }();
8352 const bool createNewParagraph = GetReturnInParagraphCreatesNewParagraph();
8353 RefPtr<HTMLBRElement> brElement;
8354 if (createNewParagraph && pointToSplit.GetContainer() == &aParentDivOrP) {
8355 // We are try to split only the current paragraph. Therefore, we don't need
8356 // to create new <br> elements around it (if left and/or right paragraph
8357 // becomes empty, it'll be treated by SplitParagraphWithTransaction().
8358 brElement = nullptr;
8359 } else if (pointToSplit.IsInTextNode()) {
8360 if (pointToSplit.IsStartOfContainer()) {
8361 // If we're splitting the paragraph at start of a text node and there is
8362 // no preceding visible <br> element, we need to create a <br> element to
8363 // keep the inline elements containing this text node.
8364 // TODO: If the parent of the text node is the splitting paragraph,
8365 // obviously we don't need to do this because empty paragraphs will
8366 // be treated by SplitParagraphWithTransaction(). In this case, we
8367 // just need to update pointToSplit for using the same path as the
8368 // previous `if` block.
8369 brElement =
8370 HTMLBRElement::FromNodeOrNull(HTMLEditUtils::GetPreviousSibling(
8371 *pointToSplit.ContainerAs<Text>(),
8372 {WalkTreeOption::IgnoreNonEditableNode}));
8373 if (!brElement || HTMLEditUtils::IsInvisibleBRElement(*brElement) ||
8374 EditorUtils::IsPaddingBRElementForEmptyLastLine(*brElement)) {
8375 // If insertParagraph does not create a new paragraph, default to
8376 // insertLineBreak.
8377 if (!createNewParagraph) {
8378 return SplitNodeResult::NotHandled(pointToSplit);
8380 const EditorDOMPoint pointToInsertBR = pointToSplit.ParentPoint();
8381 MOZ_ASSERT(pointToInsertBR.IsSet());
8382 Result<CreateElementResult, nsresult> insertBRElementResult =
8383 InsertBRElement(WithTransaction::Yes, pointToInsertBR);
8384 if (MOZ_UNLIKELY(insertBRElementResult.isErr())) {
8385 NS_WARNING(
8386 "HTMLEditor::InsertBRElement(WithTransaction::Yes) failed");
8387 return insertBRElementResult.propagateErr();
8389 // We'll collapse `Selection` to the place suggested by
8390 // SplitParagraphWithTransaction.
8391 insertBRElementResult.inspect().IgnoreCaretPointSuggestion();
8392 brElement = HTMLBRElement::FromNodeOrNull(
8393 insertBRElementResult.inspect().GetNewNode());
8395 } else if (pointToSplit.IsEndOfContainer()) {
8396 // If we're splitting the paragraph at end of a text node and there is not
8397 // following visible <br> element, we need to create a <br> element after
8398 // the text node to make current style specified by parent inline elements
8399 // keep in the right paragraph.
8400 // TODO: Same as above, we don't need to do this if the text node is a
8401 // direct child of the paragraph. For using the simplest path, we
8402 // just need to update `pointToSplit` in the case.
8403 brElement = HTMLBRElement::FromNodeOrNull(HTMLEditUtils::GetNextSibling(
8404 *pointToSplit.ContainerAs<Text>(),
8405 {WalkTreeOption::IgnoreNonEditableNode}));
8406 if (!brElement || HTMLEditUtils::IsInvisibleBRElement(*brElement) ||
8407 EditorUtils::IsPaddingBRElementForEmptyLastLine(*brElement)) {
8408 // If insertParagraph does not create a new paragraph, default to
8409 // insertLineBreak.
8410 if (!createNewParagraph) {
8411 return SplitNodeResult::NotHandled(pointToSplit);
8413 const auto pointToInsertBR =
8414 EditorDOMPoint::After(*pointToSplit.ContainerAs<Text>());
8415 MOZ_ASSERT(pointToInsertBR.IsSet());
8416 Result<CreateElementResult, nsresult> insertBRElementResult =
8417 InsertBRElement(WithTransaction::Yes, pointToInsertBR);
8418 if (MOZ_UNLIKELY(insertBRElementResult.isErr())) {
8419 NS_WARNING(
8420 "HTMLEditor::InsertBRElement(WithTransaction::Yes) failed");
8421 return insertBRElementResult.propagateErr();
8423 // We'll collapse `Selection` to the place suggested by
8424 // SplitParagraphWithTransaction.
8425 insertBRElementResult.inspect().IgnoreCaretPointSuggestion();
8426 brElement = HTMLBRElement::FromNodeOrNull(
8427 insertBRElementResult.inspect().GetNewNode());
8429 } else {
8430 // If insertParagraph does not create a new paragraph, default to
8431 // insertLineBreak.
8432 if (!createNewParagraph) {
8433 return SplitNodeResult::NotHandled(pointToSplit);
8436 // If we're splitting the paragraph at middle of a text node, we should
8437 // split the text node here and put a <br> element next to the left text
8438 // node.
8439 // XXX Why? I think that this should be handled in
8440 // SplitParagraphWithTransaction() directly because I don't find
8441 // the necessary case of the <br> element.
8443 // XXX We split a text node here if caret is middle of it to insert
8444 // <br> element **before** splitting aParentDivOrP. Then, if
8445 // the <br> element becomes unnecessary, it'll be removed again.
8446 // So this does much more complicated things than what we want to
8447 // do here. We should handle this case separately to make the code
8448 // much simpler.
8450 // Normalize collapsible white-spaces around the split point to keep
8451 // them visible after the split. Note that this does not touch
8452 // selection because of using AutoTransactionsConserveSelection in
8453 // WhiteSpaceVisibilityKeeper::ReplaceTextAndRemoveEmptyTextNodes().
8454 Result<EditorDOMPoint, nsresult> pointToSplitOrError =
8455 WhiteSpaceVisibilityKeeper::PrepareToSplitBlockElement(
8456 *this, pointToSplit, aParentDivOrP);
8457 if (NS_WARN_IF(Destroyed())) {
8458 return Err(NS_ERROR_EDITOR_DESTROYED);
8460 if (MOZ_UNLIKELY(pointToSplitOrError.isErr())) {
8461 NS_WARNING(
8462 "WhiteSpaceVisibilityKeeper::PrepareToSplitBlockElement() "
8463 "failed");
8464 return pointToSplitOrError.propagateErr();
8466 MOZ_ASSERT(pointToSplitOrError.inspect().IsSetAndValid());
8467 if (pointToSplitOrError.inspect().IsSet()) {
8468 pointToSplit = pointToSplitOrError.unwrap();
8470 Result<SplitNodeResult, nsresult> splitParentDivOrPResult =
8471 SplitNodeWithTransaction(pointToSplit);
8472 if (MOZ_UNLIKELY(splitParentDivOrPResult.isErr())) {
8473 NS_WARNING("HTMLEditor::SplitNodeWithTransaction() failed");
8474 return splitParentDivOrPResult;
8476 // We'll collapse `Selection` to the place suggested by
8477 // SplitParagraphWithTransaction.
8478 splitParentDivOrPResult.inspect().IgnoreCaretPointSuggestion();
8480 pointToSplit.SetToEndOf(
8481 splitParentDivOrPResult.inspect().GetPreviousContent());
8482 if (NS_WARN_IF(!pointToSplit.IsInContentNode())) {
8483 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
8486 // We need to put new <br> after the left node if given node was split
8487 // above.
8488 const auto pointToInsertBR =
8489 EditorDOMPoint::After(*pointToSplit.ContainerAs<nsIContent>());
8490 MOZ_ASSERT(pointToInsertBR.IsSet());
8491 Result<CreateElementResult, nsresult> insertBRElementResult =
8492 InsertBRElement(WithTransaction::Yes, pointToInsertBR);
8493 if (MOZ_UNLIKELY(insertBRElementResult.isErr())) {
8494 NS_WARNING("HTMLEditor::InsertBRElement(WithTransaction::Yes) failed");
8495 return insertBRElementResult.propagateErr();
8497 // We'll collapse `Selection` to the place suggested by
8498 // SplitParagraphWithTransaction.
8499 insertBRElementResult.inspect().IgnoreCaretPointSuggestion();
8500 brElement = HTMLBRElement::FromNodeOrNull(
8501 insertBRElementResult.inspect().GetNewNode());
8503 } else {
8504 // If we're splitting in a child element of the paragraph, and there is no
8505 // <br> element around it, we should insert a <br> element at the split
8506 // point and keep splitting the paragraph after the new <br> element.
8507 // XXX Why? We probably need to do this if we're splitting in an inline
8508 // element which and whose parents provide some styles, we should put
8509 // the <br> element for making a placeholder in the left paragraph for
8510 // moving to the caret, but I think that this could be handled in fewer
8511 // cases than this.
8512 brElement = HTMLBRElement::FromNodeOrNull(HTMLEditUtils::GetPreviousContent(
8513 pointToSplit, {WalkTreeOption::IgnoreNonEditableNode},
8514 BlockInlineCheck::Unused, &aEditingHost));
8515 if (!brElement || HTMLEditUtils::IsInvisibleBRElement(*brElement) ||
8516 EditorUtils::IsPaddingBRElementForEmptyLastLine(*brElement)) {
8517 // is there a BR after it?
8518 brElement = HTMLBRElement::FromNodeOrNull(HTMLEditUtils::GetNextContent(
8519 pointToSplit, {WalkTreeOption::IgnoreNonEditableNode},
8520 BlockInlineCheck::Unused, &aEditingHost));
8521 if (!brElement || HTMLEditUtils::IsInvisibleBRElement(*brElement) ||
8522 EditorUtils::IsPaddingBRElementForEmptyLastLine(*brElement)) {
8523 // If insertParagraph does not create a new paragraph, default to
8524 // insertLineBreak.
8525 if (!createNewParagraph) {
8526 return SplitNodeResult::NotHandled(pointToSplit);
8528 Result<CreateElementResult, nsresult> insertBRElementResult =
8529 InsertBRElement(WithTransaction::Yes, pointToSplit);
8530 if (MOZ_UNLIKELY(insertBRElementResult.isErr())) {
8531 NS_WARNING(
8532 "HTMLEditor::InsertBRElement(WithTransaction::Yes) failed");
8533 return insertBRElementResult.propagateErr();
8535 // We'll collapse `Selection` to the place suggested by
8536 // SplitParagraphWithTransaction.
8537 insertBRElementResult.inspect().IgnoreCaretPointSuggestion();
8538 brElement = HTMLBRElement::FromNodeOrNull(
8539 insertBRElementResult.inspect().GetNewNode());
8540 // We split the parent after the <br>.
8541 pointToSplit.SetAfter(brElement);
8542 if (NS_WARN_IF(!pointToSplit.IsSet())) {
8543 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
8549 Result<SplitNodeResult, nsresult> splitParagraphResult =
8550 SplitParagraphWithTransaction(aParentDivOrP, pointToSplit, brElement);
8551 if (MOZ_UNLIKELY(splitParagraphResult.isErr())) {
8552 NS_WARNING("HTMLEditor::SplitParagraphWithTransaction() failed");
8553 return splitParagraphResult;
8555 if (MOZ_UNLIKELY(!splitParagraphResult.inspect().DidSplit())) {
8556 NS_WARNING(
8557 "HTMLEditor::SplitParagraphWithTransaction() didn't split the "
8558 "paragraph");
8559 splitParagraphResult.inspect().IgnoreCaretPointSuggestion();
8560 return Err(NS_ERROR_FAILURE);
8562 MOZ_ASSERT(splitParagraphResult.inspect().Handled());
8563 return splitParagraphResult;
8566 Result<SplitNodeResult, nsresult> HTMLEditor::SplitParagraphWithTransaction(
8567 Element& aParentDivOrP, const EditorDOMPoint& aStartOfRightNode,
8568 HTMLBRElement* aMayBecomeVisibleBRElement) {
8569 MOZ_ASSERT(IsEditActionDataAvailable());
8571 Result<EditorDOMPoint, nsresult> preparationResult =
8572 WhiteSpaceVisibilityKeeper::PrepareToSplitBlockElement(
8573 *this, aStartOfRightNode, aParentDivOrP);
8574 if (MOZ_UNLIKELY(preparationResult.isErr())) {
8575 NS_WARNING(
8576 "WhiteSpaceVisibilityKeeper::PrepareToSplitBlockElement() failed");
8577 return preparationResult.propagateErr();
8579 EditorDOMPoint pointToSplit = preparationResult.unwrap();
8580 MOZ_ASSERT(pointToSplit.IsInContentNode());
8582 // Split the paragraph.
8583 Result<SplitNodeResult, nsresult> splitDivOrPResult =
8584 SplitNodeDeepWithTransaction(aParentDivOrP, pointToSplit,
8585 SplitAtEdges::eAllowToCreateEmptyContainer);
8586 if (MOZ_UNLIKELY(splitDivOrPResult.isErr())) {
8587 NS_WARNING("HTMLEditor::SplitNodeDeepWithTransaction() failed");
8588 return splitDivOrPResult;
8590 SplitNodeResult unwrappedSplitDivOrPResult = splitDivOrPResult.unwrap();
8591 if (MOZ_UNLIKELY(!unwrappedSplitDivOrPResult.DidSplit())) {
8592 NS_WARNING(
8593 "HTMLEditor::SplitNodeDeepWithTransaction() didn't split any nodes");
8594 return unwrappedSplitDivOrPResult;
8597 // We'll compute caret suggestion later. So the simple result is not needed.
8598 unwrappedSplitDivOrPResult.IgnoreCaretPointSuggestion();
8600 auto* const leftDivOrParagraphElement =
8601 unwrappedSplitDivOrPResult.GetPreviousContentAs<Element>();
8602 MOZ_ASSERT(leftDivOrParagraphElement,
8603 "SplitNodeResult::GetPreviousContent() should return something if "
8604 "DidSplit() returns true");
8605 auto* const rightDivOrParagraphElement =
8606 unwrappedSplitDivOrPResult.GetNextContentAs<Element>();
8607 MOZ_ASSERT(rightDivOrParagraphElement,
8608 "SplitNodeResult::GetNextContent() should return something if "
8609 "DidSplit() returns true");
8611 // Get rid of the break, if it is visible (otherwise it may be needed to
8612 // prevent an empty p).
8613 if (aMayBecomeVisibleBRElement &&
8614 HTMLEditUtils::IsVisibleBRElement(*aMayBecomeVisibleBRElement)) {
8615 nsresult rv = DeleteNodeWithTransaction(*aMayBecomeVisibleBRElement);
8616 if (NS_FAILED(rv)) {
8617 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
8618 return Err(rv);
8622 // Remove ID attribute on the paragraph from the right node.
8623 // MOZ_KnownLive(rightDivOrParagraphElement) because it's grabbed by
8624 // unwrappedSplitDivOrPResult.
8625 nsresult rv = RemoveAttributeWithTransaction(
8626 MOZ_KnownLive(*rightDivOrParagraphElement), *nsGkAtoms::id);
8627 if (NS_FAILED(rv)) {
8628 NS_WARNING(
8629 "EditorBase::RemoveAttributeWithTransaction(nsGkAtoms::id) failed");
8630 return Err(rv);
8633 // We need to ensure to both paragraphs visible even if they are empty.
8634 // However, padding <br> element for empty last line isn't useful in this
8635 // case because it'll be ignored by PlaintextSerializer. Additionally,
8636 // it'll be exposed as <br> with Element.innerHTML. Therefore, we can use
8637 // normal <br> elements for placeholder in this case. Note that Chromium
8638 // also behaves so.
8639 auto InsertBRElementIfEmptyBlockElement =
8640 [&](Element& aElement) MOZ_CAN_RUN_SCRIPT {
8641 if (!HTMLEditUtils::IsBlockElement(
8642 aElement, BlockInlineCheck::UseComputedDisplayStyle)) {
8643 return NS_OK;
8646 if (!HTMLEditUtils::IsEmptyNode(
8647 aElement, {EmptyCheckOption::TreatSingleBRElementAsVisible})) {
8648 return NS_OK;
8651 // XXX: Probably, we should use
8652 // InsertPaddingBRElementForEmptyLastLineWithTransaction here, and
8653 // if there are some empty inline container, we should put the <br>
8654 // into the last one.
8655 Result<CreateElementResult, nsresult> insertBRElementResult =
8656 InsertBRElement(WithTransaction::Yes,
8657 EditorDOMPoint(&aElement, 0u));
8658 if (MOZ_UNLIKELY(insertBRElementResult.isErr())) {
8659 NS_WARNING(
8660 "HTMLEditor::InsertBRElement(WithTransaction::Yes) failed");
8661 return insertBRElementResult.unwrapErr();
8663 // After this is called twice, we'll compute new caret position.
8664 // Therefore, we don't need to update selection here.
8665 insertBRElementResult.inspect().IgnoreCaretPointSuggestion();
8666 return NS_OK;
8669 // MOZ_KnownLive(leftDivOrParagraphElement) because it's grabbed by
8670 // splitDivOrResult.
8671 rv = InsertBRElementIfEmptyBlockElement(
8672 MOZ_KnownLive(*leftDivOrParagraphElement));
8673 if (NS_FAILED(rv)) {
8674 NS_WARNING(
8675 "InsertBRElementIfEmptyBlockElement(leftDivOrParagraphElement) failed");
8676 return Err(rv);
8679 if (HTMLEditUtils::IsEmptyNode(*rightDivOrParagraphElement)) {
8680 // If the right paragraph is empty, it might have an empty inline element
8681 // (which may contain other empty inline containers) and optionally a <br>
8682 // element which may not be in the deepest inline element.
8683 const RefPtr<Element> deepestInlineContainerElement =
8684 [](const Element& aBlockElement) {
8685 Element* result = nullptr;
8686 for (Element* maybeDeepestInlineContainer =
8687 Element::FromNodeOrNull(aBlockElement.GetFirstChild());
8688 maybeDeepestInlineContainer &&
8689 HTMLEditUtils::IsInlineContent(
8690 *maybeDeepestInlineContainer,
8691 BlockInlineCheck::UseComputedDisplayStyle) &&
8692 HTMLEditUtils::IsContainerNode(*maybeDeepestInlineContainer);
8693 maybeDeepestInlineContainer =
8694 maybeDeepestInlineContainer->GetFirstElementChild()) {
8695 result = maybeDeepestInlineContainer;
8697 return result;
8698 }(*rightDivOrParagraphElement);
8699 if (deepestInlineContainerElement) {
8700 RefPtr<HTMLBRElement> brElement =
8701 HTMLEditUtils::GetFirstBRElement(*rightDivOrParagraphElement);
8702 // If there is a <br> element and it is in the deepest inline container,
8703 // we need to do nothing anymore. Let's suggest caret position as at the
8704 // <br>.
8705 if (brElement &&
8706 brElement->GetParentNode() == deepestInlineContainerElement) {
8707 nsresult rv = UpdateBRElementType(
8708 *brElement, BRElementType::PaddingForEmptyLastLine);
8709 if (NS_FAILED(rv)) {
8710 NS_WARNING("EditorBase::UpdateBRElementType() failed");
8711 return Err(rv);
8713 return SplitNodeResult(std::move(unwrappedSplitDivOrPResult),
8714 EditorDOMPoint(brElement));
8716 // Otherwise, we should put a padding <br> element into the deepest inline
8717 // container and then, existing <br> element (if there is) becomes
8718 // unnecessary.
8719 if (brElement) {
8720 nsresult rv = DeleteNodeWithTransaction(*brElement);
8721 if (NS_FAILED(rv)) {
8722 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
8723 return Err(rv);
8726 Result<CreateElementResult, nsresult> insertPaddingBRElementResult =
8727 InsertPaddingBRElementForEmptyLastLineWithTransaction(
8728 EditorDOMPoint::AtEndOf(deepestInlineContainerElement));
8729 if (MOZ_UNLIKELY(insertPaddingBRElementResult.isErr())) {
8730 NS_WARNING(
8731 "HTMLEditor::"
8732 "InsertPaddingBRElementForEmptyLastLineWithTransaction() failed");
8733 return insertPaddingBRElementResult.propagateErr();
8735 insertPaddingBRElementResult.inspect().IgnoreCaretPointSuggestion();
8736 return SplitNodeResult(
8737 std::move(unwrappedSplitDivOrPResult),
8738 EditorDOMPoint(insertPaddingBRElementResult.inspect().GetNewNode()));
8741 // If there is no inline container elements, we just need to make the
8742 // right paragraph visible.
8743 nsresult rv = InsertBRElementIfEmptyBlockElement(
8744 MOZ_KnownLive(*rightDivOrParagraphElement));
8745 if (NS_FAILED(rv)) {
8746 NS_WARNING(
8747 "InsertBRElementIfEmptyBlockElement(rightDivOrParagraphElement) "
8748 "failed");
8749 return Err(rv);
8753 // Let's put caret at start of the first leaf container.
8754 nsIContent* child = HTMLEditUtils::GetFirstLeafContent(
8755 *rightDivOrParagraphElement, {LeafNodeType::LeafNodeOrChildBlock},
8756 BlockInlineCheck::UseComputedDisplayStyle);
8757 if (MOZ_UNLIKELY(!child)) {
8758 return SplitNodeResult(std::move(unwrappedSplitDivOrPResult),
8759 EditorDOMPoint(rightDivOrParagraphElement, 0u));
8761 return child->IsText() || HTMLEditUtils::IsContainerNode(*child)
8762 ? SplitNodeResult(std::move(unwrappedSplitDivOrPResult),
8763 EditorDOMPoint(child, 0u))
8764 : SplitNodeResult(std::move(unwrappedSplitDivOrPResult),
8765 EditorDOMPoint(child));
8768 Result<InsertParagraphResult, nsresult>
8769 HTMLEditor::HandleInsertParagraphInListItemElement(
8770 Element& aListItemElement, const EditorDOMPoint& aPointToSplit,
8771 const Element& aEditingHost) {
8772 MOZ_ASSERT(IsEditActionDataAvailable());
8773 MOZ_ASSERT(HTMLEditUtils::IsListItem(&aListItemElement));
8775 // If aListItemElement is empty, then we want to outdent its content.
8776 if (&aEditingHost != aListItemElement.GetParentElement() &&
8777 HTMLEditUtils::IsEmptyBlockElement(
8778 aListItemElement,
8779 {EmptyCheckOption::TreatNonEditableContentAsInvisible},
8780 BlockInlineCheck::UseComputedDisplayOutsideStyle)) {
8781 RefPtr<Element> leftListElement = aListItemElement.GetParentElement();
8782 // If the given list item element is not the last list item element of
8783 // its parent nor not followed by sub list elements, split the parent
8784 // before it.
8785 if (!HTMLEditUtils::IsLastChild(aListItemElement,
8786 {WalkTreeOption::IgnoreNonEditableNode})) {
8787 Result<SplitNodeResult, nsresult> splitListItemParentResult =
8788 SplitNodeWithTransaction(EditorDOMPoint(&aListItemElement));
8789 if (MOZ_UNLIKELY(splitListItemParentResult.isErr())) {
8790 NS_WARNING("HTMLEditor::SplitNodeWithTransaction() failed");
8791 return splitListItemParentResult.propagateErr();
8793 SplitNodeResult unwrappedSplitListItemParentResult =
8794 splitListItemParentResult.unwrap();
8795 if (MOZ_UNLIKELY(!unwrappedSplitListItemParentResult.DidSplit())) {
8796 NS_WARNING(
8797 "HTMLEditor::SplitNodeWithTransaction() didn't split the parent of "
8798 "aListItemElement");
8799 MOZ_ASSERT(
8800 !unwrappedSplitListItemParentResult.HasCaretPointSuggestion());
8801 return Err(NS_ERROR_FAILURE);
8803 unwrappedSplitListItemParentResult.IgnoreCaretPointSuggestion();
8804 leftListElement =
8805 unwrappedSplitListItemParentResult.GetPreviousContentAs<Element>();
8806 MOZ_DIAGNOSTIC_ASSERT(leftListElement);
8809 auto afterLeftListElement = EditorDOMPoint::After(leftListElement);
8810 if (MOZ_UNLIKELY(!afterLeftListElement.IsSet())) {
8811 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
8814 // If aListItemElement is in an invalid sub-list element, move it into
8815 // the grand parent list element in order to outdent.
8816 if (HTMLEditUtils::IsAnyListElement(afterLeftListElement.GetContainer())) {
8817 Result<MoveNodeResult, nsresult> moveListItemElementResult =
8818 MoveNodeWithTransaction(aListItemElement, afterLeftListElement);
8819 if (MOZ_UNLIKELY(moveListItemElementResult.isErr())) {
8820 NS_WARNING("HTMLEditor::MoveNodeWithTransaction() failed");
8821 return moveListItemElementResult.propagateErr();
8823 moveListItemElementResult.inspect().IgnoreCaretPointSuggestion();
8824 return InsertParagraphResult(&aListItemElement,
8825 EditorDOMPoint(&aListItemElement, 0u));
8828 // Otherwise, replace the empty aListItemElement with a new paragraph.
8829 nsresult rv = DeleteNodeWithTransaction(aListItemElement);
8830 if (NS_FAILED(rv)) {
8831 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
8832 return Err(rv);
8834 nsStaticAtom& newParagraphTagName =
8835 &DefaultParagraphSeparatorTagName() == nsGkAtoms::br
8836 ? *nsGkAtoms::p
8837 : DefaultParagraphSeparatorTagName();
8838 // MOZ_KnownLive(newParagraphTagName) because it's available until shutdown.
8839 Result<CreateElementResult, nsresult> createNewParagraphElementResult =
8840 CreateAndInsertElement(
8841 WithTransaction::Yes, MOZ_KnownLive(newParagraphTagName),
8842 afterLeftListElement, HTMLEditor::InsertNewBRElement);
8843 if (MOZ_UNLIKELY(createNewParagraphElementResult.isErr())) {
8844 NS_WARNING(
8845 "HTMLEditor::CreateAndInsertElement(WithTransaction::Yes) failed");
8846 return createNewParagraphElementResult.propagateErr();
8848 createNewParagraphElementResult.inspect().IgnoreCaretPointSuggestion();
8849 MOZ_ASSERT(createNewParagraphElementResult.inspect().GetNewNode());
8850 EditorDOMPoint pointToPutCaret(
8851 createNewParagraphElementResult.inspect().GetNewNode(), 0u);
8852 return InsertParagraphResult(
8853 createNewParagraphElementResult.inspect().GetNewNode(),
8854 std::move(pointToPutCaret));
8857 // If aListItemElement has some content or aListItemElement is empty but it's
8858 // a child of editing host, we want a new list item at the same list level.
8859 // First, sort out white-spaces.
8860 Result<EditorDOMPoint, nsresult> preparationResult =
8861 WhiteSpaceVisibilityKeeper::PrepareToSplitBlockElement(
8862 *this, aPointToSplit, aListItemElement);
8863 if (preparationResult.isErr()) {
8864 NS_WARNING(
8865 "WhiteSpaceVisibilityKeeper::PrepareToSplitBlockElement() failed");
8866 return Err(preparationResult.unwrapErr());
8868 EditorDOMPoint pointToSplit = preparationResult.unwrap();
8869 MOZ_ASSERT(pointToSplit.IsInContentNode());
8871 // Now split the list item.
8872 Result<SplitNodeResult, nsresult> splitListItemResult =
8873 SplitNodeDeepWithTransaction(aListItemElement, pointToSplit,
8874 SplitAtEdges::eAllowToCreateEmptyContainer);
8875 if (MOZ_UNLIKELY(splitListItemResult.isErr())) {
8876 NS_WARNING("HTMLEditor::SplitNodeDeepWithTransaction() failed");
8877 return splitListItemResult.propagateErr();
8879 SplitNodeResult unwrappedSplitListItemElement = splitListItemResult.unwrap();
8880 unwrappedSplitListItemElement.IgnoreCaretPointSuggestion();
8881 if (MOZ_UNLIKELY(!aListItemElement.GetParent())) {
8882 NS_WARNING("Somebody disconnected the target listitem from the parent");
8883 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
8886 // If aListItemElement is not replaced, we should not do anything anymore.
8887 if (MOZ_UNLIKELY(!unwrappedSplitListItemElement.DidSplit()) ||
8888 NS_WARN_IF(!unwrappedSplitListItemElement.GetNewContentAs<Element>()) ||
8889 NS_WARN_IF(
8890 !unwrappedSplitListItemElement.GetOriginalContentAs<Element>())) {
8891 NS_WARNING("HTMLEditor::SplitNodeDeepWithTransaction() didn't split");
8892 return Err(NS_ERROR_FAILURE);
8895 // FYI: They are grabbed by unwrappedSplitListItemElement so that they are
8896 // known live
8897 // things.
8898 auto& leftListItemElement =
8899 *unwrappedSplitListItemElement.GetPreviousContentAs<Element>();
8900 auto& rightListItemElement =
8901 *unwrappedSplitListItemElement.GetNextContentAs<Element>();
8903 // Hack: until I can change the damaged doc range code back to being
8904 // extra-inclusive, I have to manually detect certain list items that may be
8905 // left empty.
8906 if (HTMLEditUtils::IsEmptyNode(
8907 leftListItemElement,
8908 {EmptyCheckOption::TreatSingleBRElementAsVisible,
8909 EmptyCheckOption::TreatNonEditableContentAsInvisible})) {
8910 Result<CreateElementResult, nsresult> insertPaddingBRElementResult =
8911 InsertPaddingBRElementForEmptyLastLineWithTransaction(
8912 EditorDOMPoint(&leftListItemElement, 0u));
8913 if (MOZ_UNLIKELY(insertPaddingBRElementResult.isErr())) {
8914 NS_WARNING(
8915 "HTMLEditor::InsertPaddingBRElementForEmptyLastLineWithTransaction("
8916 ") failed");
8917 return insertPaddingBRElementResult.propagateErr();
8919 // We're returning a candidate point to put caret so that we don't need to
8920 // update now.
8921 insertPaddingBRElementResult.inspect().IgnoreCaretPointSuggestion();
8922 return InsertParagraphResult(&rightListItemElement,
8923 EditorDOMPoint(&rightListItemElement, 0u));
8926 if (HTMLEditUtils::IsEmptyNode(
8927 rightListItemElement,
8928 {EmptyCheckOption::TreatNonEditableContentAsInvisible})) {
8929 // If aListItemElement is a <dd> or a <dt> and the right list item is empty
8930 // or a direct child of the editing host, replace it a new list item element
8931 // whose type is the other one.
8932 if (aListItemElement.IsAnyOfHTMLElements(nsGkAtoms::dd, nsGkAtoms::dt)) {
8933 nsStaticAtom& nextDefinitionListItemTagName =
8934 aListItemElement.IsHTMLElement(nsGkAtoms::dt) ? *nsGkAtoms::dd
8935 : *nsGkAtoms::dt;
8936 // MOZ_KnownLive(nextDefinitionListItemTagName) because it's available
8937 // until shutdown.
8938 Result<CreateElementResult, nsresult> createNewListItemElementResult =
8939 CreateAndInsertElement(WithTransaction::Yes,
8940 MOZ_KnownLive(nextDefinitionListItemTagName),
8941 EditorDOMPoint::After(rightListItemElement));
8942 if (MOZ_UNLIKELY(createNewListItemElementResult.isErr())) {
8943 NS_WARNING(
8944 "HTMLEditor::CreateAndInsertElement(WithTransaction::Yes) failed");
8945 return createNewListItemElementResult.propagateErr();
8947 CreateElementResult unwrappedCreateNewListItemElementResult =
8948 createNewListItemElementResult.unwrap();
8949 unwrappedCreateNewListItemElementResult.IgnoreCaretPointSuggestion();
8950 RefPtr<Element> newListItemElement =
8951 unwrappedCreateNewListItemElementResult.UnwrapNewNode();
8952 MOZ_ASSERT(newListItemElement);
8953 // MOZ_KnownLive(rightListItemElement) because it's grabbed by
8954 // unwrappedSplitListItemElement.
8955 nsresult rv =
8956 DeleteNodeWithTransaction(MOZ_KnownLive(rightListItemElement));
8957 if (NS_FAILED(rv)) {
8958 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
8959 return Err(rv);
8961 EditorDOMPoint pointToPutCaret(newListItemElement, 0u);
8962 return InsertParagraphResult(std::move(newListItemElement),
8963 std::move(pointToPutCaret));
8966 // If aListItemElement is a <li> and the right list item becomes empty or a
8967 // direct child of the editing host, copy all inline elements affecting to
8968 // the style at end of the left list item element to the right list item
8969 // element.
8970 // MOZ_KnownLive(leftListItemElement) and
8971 // MOZ_KnownLive(rightListItemElement) because they are grabbed by
8972 // unwrappedSplitListItemElement.
8973 Result<EditorDOMPoint, nsresult> pointToPutCaretOrError =
8974 CopyLastEditableChildStylesWithTransaction(
8975 MOZ_KnownLive(leftListItemElement),
8976 MOZ_KnownLive(rightListItemElement), aEditingHost);
8977 if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) {
8978 NS_WARNING(
8979 "HTMLEditor::CopyLastEditableChildStylesWithTransaction() failed");
8980 return pointToPutCaretOrError.propagateErr();
8982 return InsertParagraphResult(&rightListItemElement,
8983 pointToPutCaretOrError.unwrap());
8986 // If the right list item element is not empty, we need to consider where to
8987 // put caret in it. If it has non-container inline elements, <br> or <hr>, at
8988 // the element is proper position.
8989 const WSScanResult forwardScanFromStartOfListItemResult =
8990 WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary(
8991 &aEditingHost, EditorRawDOMPoint(&rightListItemElement, 0u),
8992 BlockInlineCheck::UseComputedDisplayStyle);
8993 if (MOZ_UNLIKELY(forwardScanFromStartOfListItemResult.Failed())) {
8994 NS_WARNING("WSRunScanner::ScanNextVisibleNodeOrBlockBoundary() failed");
8995 return Err(NS_ERROR_FAILURE);
8997 if (forwardScanFromStartOfListItemResult.ReachedSpecialContent() ||
8998 forwardScanFromStartOfListItemResult.ReachedBRElement() ||
8999 forwardScanFromStartOfListItemResult.ReachedHRElement()) {
9000 auto atFoundElement = forwardScanFromStartOfListItemResult
9001 .PointAtReachedContent<EditorDOMPoint>();
9002 if (NS_WARN_IF(!atFoundElement.IsSetAndValid())) {
9003 return Err(NS_ERROR_FAILURE);
9005 return InsertParagraphResult(&rightListItemElement,
9006 std::move(atFoundElement));
9009 // If we reached a block boundary (end of the list item or a child block),
9010 // let's put deepest start of the list item or the child block.
9011 if (forwardScanFromStartOfListItemResult.ReachedBlockBoundary() ||
9012 // FIXME: This is wrong considering because the inline editing host may
9013 // be surrounded by visible inline content. However, WSRunScanner is
9014 // not aware of block boundary around it and stopping this change causes
9015 // starting to fail some WPT. Therefore, we need to keep doing this for
9016 // now.
9017 forwardScanFromStartOfListItemResult.ReachedInlineEditingHostBoundary()) {
9018 return InsertParagraphResult(
9019 &rightListItemElement,
9020 HTMLEditUtils::GetDeepestEditableStartPointOf<EditorDOMPoint>(
9021 forwardScanFromStartOfListItemResult.GetContent()
9022 ? *forwardScanFromStartOfListItemResult.GetContent()
9023 : rightListItemElement));
9026 // Otherwise, return the point at first visible thing.
9027 // XXX This may be not meaningful position if it reached block element
9028 // in aListItemElement.
9029 return InsertParagraphResult(&rightListItemElement,
9030 forwardScanFromStartOfListItemResult
9031 .PointAtReachedContent<EditorDOMPoint>());
9034 Result<CreateElementResult, nsresult>
9035 HTMLEditor::WrapContentsInBlockquoteElementsWithTransaction(
9036 const nsTArray<OwningNonNull<nsIContent>>& aArrayOfContents,
9037 const Element& aEditingHost) {
9038 MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable());
9040 // The idea here is to put the nodes into a minimal number of blockquotes.
9041 // When the user blockquotes something, they expect one blockquote. That
9042 // may not be possible (for instance, if they have two table cells selected,
9043 // you need two blockquotes inside the cells).
9044 RefPtr<Element> curBlock, blockElementToPutCaret;
9045 nsCOMPtr<nsINode> prevParent;
9047 EditorDOMPoint pointToPutCaret;
9048 for (auto& content : aArrayOfContents) {
9049 // If the node is a table element or list item, dive inside
9050 if (HTMLEditUtils::IsAnyTableElementButNotTable(content) ||
9051 HTMLEditUtils::IsListItem(content)) {
9052 // Forget any previous block
9053 curBlock = nullptr;
9054 // Recursion time
9055 AutoTArray<OwningNonNull<nsIContent>, 24> childContents;
9056 HTMLEditUtils::CollectAllChildren(*content, childContents);
9057 Result<CreateElementResult, nsresult>
9058 wrapChildrenInAnotherBlockquoteResult =
9059 WrapContentsInBlockquoteElementsWithTransaction(childContents,
9060 aEditingHost);
9061 if (MOZ_UNLIKELY(wrapChildrenInAnotherBlockquoteResult.isErr())) {
9062 NS_WARNING(
9063 "HTMLEditor::WrapContentsInBlockquoteElementsWithTransaction() "
9064 "failed");
9065 return wrapChildrenInAnotherBlockquoteResult;
9067 CreateElementResult unwrappedWrapChildrenInAnotherBlockquoteResult =
9068 wrapChildrenInAnotherBlockquoteResult.unwrap();
9069 unwrappedWrapChildrenInAnotherBlockquoteResult.MoveCaretPointTo(
9070 pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
9071 if (unwrappedWrapChildrenInAnotherBlockquoteResult.GetNewNode()) {
9072 blockElementToPutCaret =
9073 unwrappedWrapChildrenInAnotherBlockquoteResult.UnwrapNewNode();
9077 // If the node has different parent than previous node, further nodes in a
9078 // new parent
9079 if (prevParent) {
9080 if (prevParent != content->GetParentNode()) {
9081 // Forget any previous blockquote node we were using
9082 curBlock = nullptr;
9083 prevParent = content->GetParentNode();
9085 } else {
9086 prevParent = content->GetParentNode();
9089 // If no curBlock, make one
9090 if (!curBlock) {
9091 Result<CreateElementResult, nsresult> createNewBlockquoteElementResult =
9092 InsertElementWithSplittingAncestorsWithTransaction(
9093 *nsGkAtoms::blockquote, EditorDOMPoint(content),
9094 BRElementNextToSplitPoint::Keep, aEditingHost);
9095 if (MOZ_UNLIKELY(createNewBlockquoteElementResult.isErr())) {
9096 NS_WARNING(
9097 "HTMLEditor::InsertElementWithSplittingAncestorsWithTransaction("
9098 "nsGkAtoms::blockquote) failed");
9099 return createNewBlockquoteElementResult;
9101 CreateElementResult unwrappedCreateNewBlockquoteElementResult =
9102 createNewBlockquoteElementResult.unwrap();
9103 unwrappedCreateNewBlockquoteElementResult.MoveCaretPointTo(
9104 pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
9105 MOZ_ASSERT(unwrappedCreateNewBlockquoteElementResult.GetNewNode());
9106 blockElementToPutCaret =
9107 unwrappedCreateNewBlockquoteElementResult.GetNewNode();
9108 curBlock = unwrappedCreateNewBlockquoteElementResult.UnwrapNewNode();
9111 // MOZ_KnownLive because 'aArrayOfContents' is guaranteed to/ keep it alive.
9112 Result<MoveNodeResult, nsresult> moveNodeResult =
9113 MoveNodeToEndWithTransaction(MOZ_KnownLive(content), *curBlock);
9114 if (MOZ_UNLIKELY(moveNodeResult.isErr())) {
9115 NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed");
9116 return moveNodeResult.propagateErr();
9118 MoveNodeResult unwrappedMoveNodeResult = moveNodeResult.unwrap();
9119 unwrappedMoveNodeResult.MoveCaretPointTo(
9120 pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
9122 return blockElementToPutCaret
9123 ? CreateElementResult(std::move(blockElementToPutCaret),
9124 std::move(pointToPutCaret))
9125 : CreateElementResult::NotHandled(std::move(pointToPutCaret));
9128 Result<EditorDOMPoint, nsresult>
9129 HTMLEditor::RemoveBlockContainerElementsWithTransaction(
9130 const nsTArray<OwningNonNull<nsIContent>>& aArrayOfContents,
9131 FormatBlockMode aFormatBlockMode, BlockInlineCheck aBlockInlineCheck) {
9132 MOZ_ASSERT(IsEditActionDataAvailable());
9133 MOZ_ASSERT(aFormatBlockMode == FormatBlockMode::XULParagraphStateCommand);
9135 // Intent of this routine is to be used for converting to/from headers,
9136 // paragraphs, pre, and address. Those blocks that pretty much just contain
9137 // inline things...
9138 RefPtr<Element> blockElement;
9139 nsCOMPtr<nsIContent> firstContent, lastContent;
9140 EditorDOMPoint pointToPutCaret;
9141 for (const auto& content : aArrayOfContents) {
9142 // If the current node is a format element, remove it.
9143 if (HTMLEditUtils::IsFormatElementForParagraphStateCommand(content)) {
9144 // Process any partial progress saved
9145 if (blockElement) {
9146 Result<SplitRangeOffFromNodeResult, nsresult> unwrapBlockElementResult =
9147 RemoveBlockContainerElementWithTransactionBetween(
9148 *blockElement, *firstContent, *lastContent, aBlockInlineCheck);
9149 if (MOZ_UNLIKELY(unwrapBlockElementResult.isErr())) {
9150 NS_WARNING(
9151 "HTMLEditor::RemoveBlockContainerElementWithTransactionBetween() "
9152 "failed");
9153 return unwrapBlockElementResult.propagateErr();
9155 unwrapBlockElementResult.unwrap().MoveCaretPointTo(
9156 pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
9157 firstContent = lastContent = blockElement = nullptr;
9159 if (!EditorUtils::IsEditableContent(content, EditorType::HTML)) {
9160 continue;
9162 // Remove current block
9163 Result<EditorDOMPoint, nsresult> unwrapFormatBlockResult =
9164 RemoveBlockContainerWithTransaction(
9165 MOZ_KnownLive(*content->AsElement()));
9166 if (MOZ_UNLIKELY(unwrapFormatBlockResult.isErr())) {
9167 NS_WARNING("HTMLEditor::RemoveBlockContainerWithTransaction() failed");
9168 return unwrapFormatBlockResult;
9170 if (unwrapFormatBlockResult.inspect().IsSet()) {
9171 pointToPutCaret = unwrapFormatBlockResult.unwrap();
9173 continue;
9176 // XXX How about, <th>, <thead>, <tfoot>, <dt>, <dl>?
9177 if (content->IsAnyOfHTMLElements(
9178 nsGkAtoms::table, nsGkAtoms::tr, nsGkAtoms::tbody, nsGkAtoms::td,
9179 nsGkAtoms::li, nsGkAtoms::blockquote, nsGkAtoms::div) ||
9180 HTMLEditUtils::IsAnyListElement(content)) {
9181 // Process any partial progress saved
9182 if (blockElement) {
9183 Result<SplitRangeOffFromNodeResult, nsresult> unwrapBlockElementResult =
9184 RemoveBlockContainerElementWithTransactionBetween(
9185 *blockElement, *firstContent, *lastContent, aBlockInlineCheck);
9186 if (MOZ_UNLIKELY(unwrapBlockElementResult.isErr())) {
9187 NS_WARNING(
9188 "HTMLEditor::RemoveBlockContainerElementWithTransactionBetween() "
9189 "failed");
9190 return unwrapBlockElementResult.propagateErr();
9192 unwrapBlockElementResult.unwrap().MoveCaretPointTo(
9193 pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
9194 firstContent = lastContent = blockElement = nullptr;
9196 if (!EditorUtils::IsEditableContent(content, EditorType::HTML)) {
9197 continue;
9199 // Recursion time
9200 AutoTArray<OwningNonNull<nsIContent>, 24> childContents;
9201 HTMLEditUtils::CollectAllChildren(*content, childContents);
9202 Result<EditorDOMPoint, nsresult> removeBlockContainerElementsResult =
9203 RemoveBlockContainerElementsWithTransaction(
9204 childContents, aFormatBlockMode, aBlockInlineCheck);
9205 if (MOZ_UNLIKELY(removeBlockContainerElementsResult.isErr())) {
9206 NS_WARNING(
9207 "HTMLEditor::RemoveBlockContainerElementsWithTransaction() failed");
9208 return removeBlockContainerElementsResult;
9210 if (removeBlockContainerElementsResult.inspect().IsSet()) {
9211 pointToPutCaret = removeBlockContainerElementsResult.unwrap();
9213 continue;
9216 if (HTMLEditUtils::IsInlineContent(content, aBlockInlineCheck)) {
9217 if (blockElement) {
9218 // If so, is this node a descendant?
9219 if (EditorUtils::IsDescendantOf(*content, *blockElement)) {
9220 // Then we don't need to do anything different for this node
9221 lastContent = content;
9222 continue;
9224 // Otherwise, we have progressed beyond end of blockElement, so let's
9225 // handle it now. We need to remove the portion of blockElement that
9226 // contains [firstContent - lastContent].
9227 Result<SplitRangeOffFromNodeResult, nsresult> unwrapBlockElementResult =
9228 RemoveBlockContainerElementWithTransactionBetween(
9229 *blockElement, *firstContent, *lastContent, aBlockInlineCheck);
9230 if (MOZ_UNLIKELY(unwrapBlockElementResult.isErr())) {
9231 NS_WARNING(
9232 "HTMLEditor::RemoveBlockContainerElementWithTransactionBetween() "
9233 "failed");
9234 return unwrapBlockElementResult.propagateErr();
9236 unwrapBlockElementResult.unwrap().MoveCaretPointTo(
9237 pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
9238 firstContent = lastContent = blockElement = nullptr;
9239 // Fall out and handle content
9241 blockElement = HTMLEditUtils::GetAncestorElement(
9242 content, HTMLEditUtils::ClosestEditableBlockElement,
9243 aBlockInlineCheck);
9244 if (!blockElement ||
9245 !HTMLEditUtils::IsFormatElementForParagraphStateCommand(
9246 *blockElement) ||
9247 !HTMLEditUtils::IsRemovableNode(*blockElement)) {
9248 // Not a block kind that we care about.
9249 blockElement = nullptr;
9250 } else {
9251 firstContent = lastContent = content;
9253 continue;
9256 if (blockElement) {
9257 // Some node that is already sans block style. Skip over it and process
9258 // any partial progress saved.
9259 Result<SplitRangeOffFromNodeResult, nsresult> unwrapBlockElementResult =
9260 RemoveBlockContainerElementWithTransactionBetween(
9261 *blockElement, *firstContent, *lastContent, aBlockInlineCheck);
9262 if (MOZ_UNLIKELY(unwrapBlockElementResult.isErr())) {
9263 NS_WARNING(
9264 "HTMLEditor::RemoveBlockContainerElementWithTransactionBetween() "
9265 "failed");
9266 return unwrapBlockElementResult.propagateErr();
9268 unwrapBlockElementResult.unwrap().MoveCaretPointTo(
9269 pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
9270 firstContent = lastContent = blockElement = nullptr;
9271 continue;
9274 // Process any partial progress saved
9275 if (blockElement) {
9276 Result<SplitRangeOffFromNodeResult, nsresult> unwrapBlockElementResult =
9277 RemoveBlockContainerElementWithTransactionBetween(
9278 *blockElement, *firstContent, *lastContent, aBlockInlineCheck);
9279 if (MOZ_UNLIKELY(unwrapBlockElementResult.isErr())) {
9280 NS_WARNING(
9281 "HTMLEditor::RemoveBlockContainerElementWithTransactionBetween() "
9282 "failed");
9283 return unwrapBlockElementResult.propagateErr();
9285 unwrapBlockElementResult.unwrap().MoveCaretPointTo(
9286 pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
9287 firstContent = lastContent = blockElement = nullptr;
9289 return pointToPutCaret;
9292 Result<CreateElementResult, nsresult>
9293 HTMLEditor::CreateOrChangeFormatContainerElement(
9294 nsTArray<OwningNonNull<nsIContent>>& aArrayOfContents,
9295 const nsStaticAtom& aNewFormatTagName, FormatBlockMode aFormatBlockMode,
9296 const Element& aEditingHost) {
9297 MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable());
9299 // Intent of this routine is to be used for converting to/from headers,
9300 // paragraphs, pre, and address. Those blocks that pretty much just contain
9301 // inline things...
9302 RefPtr<Element> newBlock, curBlock, blockElementToPutCaret;
9303 // If we found a <br> element which should be moved into curBlock, this keeps
9304 // storing the <br> element after removing it from the tree.
9305 RefPtr<Element> pendingBRElementToMoveCurBlock;
9306 EditorDOMPoint pointToPutCaret;
9307 for (auto& content : aArrayOfContents) {
9308 EditorDOMPoint atContent(content);
9309 if (NS_WARN_IF(!atContent.IsInContentNode())) {
9310 // If given node has been removed from the document, let's ignore it
9311 // since the following code may need its parent replace it with new
9312 // block.
9313 curBlock = nullptr;
9314 newBlock = nullptr;
9315 pendingBRElementToMoveCurBlock = nullptr;
9316 continue;
9319 // Is it already the right kind of block, or an uneditable block?
9320 if (content->IsHTMLElement(&aNewFormatTagName) ||
9321 (!EditorUtils::IsEditableContent(content, EditorType::HTML) &&
9322 HTMLEditUtils::IsBlockElement(
9323 content, BlockInlineCheck::UseHTMLDefaultStyle))) {
9324 // Forget any previous block used for previous inline nodes
9325 curBlock = nullptr;
9326 pendingBRElementToMoveCurBlock = nullptr;
9327 // Do nothing to this block
9328 continue;
9331 // If content is a format element, replace it with a new block of correct
9332 // type.
9333 // XXX: pre can't hold everything the others can
9334 if (HTMLEditUtils::IsMozDiv(content) ||
9335 HTMLEditor::IsFormatElement(aFormatBlockMode, content)) {
9336 // Forget any previous block used for previous inline nodes
9337 curBlock = nullptr;
9338 pendingBRElementToMoveCurBlock = nullptr;
9339 RefPtr<Element> expectedContainerOfNewBlock =
9340 atContent.IsContainerHTMLElement(nsGkAtoms::dl) &&
9341 HTMLEditUtils::IsSplittableNode(
9342 *atContent.ContainerAs<Element>())
9343 ? atContent.GetContainerParentAs<Element>()
9344 : atContent.GetContainerAs<Element>();
9345 Result<CreateElementResult, nsresult> replaceWithNewBlockElementResult =
9346 ReplaceContainerAndCloneAttributesWithTransaction(
9347 MOZ_KnownLive(*content->AsElement()), aNewFormatTagName);
9348 if (MOZ_UNLIKELY(replaceWithNewBlockElementResult.isErr())) {
9349 NS_WARNING(
9350 "EditorBase::ReplaceContainerAndCloneAttributesWithTransaction() "
9351 "failed");
9352 return replaceWithNewBlockElementResult;
9354 CreateElementResult unwrappedReplaceWithNewBlockElementResult =
9355 replaceWithNewBlockElementResult.unwrap();
9356 // If the new block element was moved to different element or removed by
9357 // the web app via mutation event listener, we should stop handling this
9358 // action since we cannot handle each of a lot of edge cases.
9359 if (NS_WARN_IF(unwrappedReplaceWithNewBlockElementResult.GetNewNode()
9360 ->GetParentNode() != expectedContainerOfNewBlock)) {
9361 unwrappedReplaceWithNewBlockElementResult.IgnoreCaretPointSuggestion();
9362 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
9364 unwrappedReplaceWithNewBlockElementResult.MoveCaretPointTo(
9365 pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
9366 newBlock = unwrappedReplaceWithNewBlockElementResult.UnwrapNewNode();
9367 continue;
9370 if (HTMLEditUtils::IsTable(content) ||
9371 HTMLEditUtils::IsAnyListElement(content) ||
9372 content->IsAnyOfHTMLElements(nsGkAtoms::tbody, nsGkAtoms::tr,
9373 nsGkAtoms::td, nsGkAtoms::li,
9374 nsGkAtoms::blockquote, nsGkAtoms::div)) {
9375 // Forget any previous block used for previous inline nodes
9376 curBlock = nullptr;
9377 pendingBRElementToMoveCurBlock = nullptr;
9378 // Recursion time
9379 AutoTArray<OwningNonNull<nsIContent>, 24> childContents;
9380 HTMLEditUtils::CollectAllChildren(*content, childContents);
9381 if (!childContents.IsEmpty()) {
9382 Result<CreateElementResult, nsresult> wrapChildrenInBlockElementResult =
9383 CreateOrChangeFormatContainerElement(
9384 childContents, aNewFormatTagName, aFormatBlockMode,
9385 aEditingHost);
9386 if (MOZ_UNLIKELY(wrapChildrenInBlockElementResult.isErr())) {
9387 NS_WARNING(
9388 "HTMLEditor::CreateOrChangeFormatContainerElement() failed");
9389 return wrapChildrenInBlockElementResult;
9391 CreateElementResult unwrappedWrapChildrenInBlockElementResult =
9392 wrapChildrenInBlockElementResult.unwrap();
9393 unwrappedWrapChildrenInBlockElementResult.MoveCaretPointTo(
9394 pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
9395 if (unwrappedWrapChildrenInBlockElementResult.GetNewNode()) {
9396 blockElementToPutCaret =
9397 unwrappedWrapChildrenInBlockElementResult.UnwrapNewNode();
9399 continue;
9402 // Make sure we can put a block here
9403 Result<CreateElementResult, nsresult> createNewBlockElementResult =
9404 InsertElementWithSplittingAncestorsWithTransaction(
9405 aNewFormatTagName, atContent, BRElementNextToSplitPoint::Keep,
9406 aEditingHost);
9407 if (MOZ_UNLIKELY(createNewBlockElementResult.isErr())) {
9408 NS_WARNING(
9409 nsPrintfCString(
9410 "HTMLEditor::"
9411 "InsertElementWithSplittingAncestorsWithTransaction(%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 MOZ_ASSERT(unwrappedCreateNewBlockElementResult.GetNewNode());
9421 blockElementToPutCaret =
9422 unwrappedCreateNewBlockElementResult.UnwrapNewNode();
9423 continue;
9426 if (content->IsHTMLElement(nsGkAtoms::br)) {
9427 if (curBlock) {
9428 if (aFormatBlockMode == FormatBlockMode::XULParagraphStateCommand) {
9429 // If the node is a break, we honor it by putting further nodes in a
9430 // new parent.
9432 // Forget any previous block used for previous inline nodes.
9433 curBlock = nullptr;
9434 pendingBRElementToMoveCurBlock = nullptr;
9435 } else {
9436 // If the node is a break, we need to move it into end of the curBlock
9437 // if we'll move following content into curBlock.
9438 pendingBRElementToMoveCurBlock = content->AsElement();
9440 // MOZ_KnownLive because 'aArrayOfContents' is guaranteed to keep it
9441 // alive.
9442 nsresult rv = DeleteNodeWithTransaction(MOZ_KnownLive(*content));
9443 if (NS_FAILED(rv)) {
9444 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
9445 return Err(rv);
9447 continue;
9450 // The break is the first (or even only) node we encountered. Create a
9451 // block for it.
9452 Result<CreateElementResult, nsresult> createNewBlockElementResult =
9453 InsertElementWithSplittingAncestorsWithTransaction(
9454 aNewFormatTagName, atContent, BRElementNextToSplitPoint::Keep,
9455 aEditingHost);
9456 if (MOZ_UNLIKELY(createNewBlockElementResult.isErr())) {
9457 NS_WARNING(nsPrintfCString("HTMLEditor::"
9458 "InsertElementWithSplittingAncestorsWith"
9459 "Transaction(%s) failed",
9460 nsAtomCString(&aNewFormatTagName).get())
9461 .get());
9462 return createNewBlockElementResult;
9464 CreateElementResult unwrappedCreateNewBlockElementResult =
9465 createNewBlockElementResult.unwrap();
9466 unwrappedCreateNewBlockElementResult.MoveCaretPointTo(
9467 pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
9468 RefPtr<Element> newBlockElement =
9469 unwrappedCreateNewBlockElementResult.UnwrapNewNode();
9470 MOZ_ASSERT(newBlockElement);
9471 blockElementToPutCaret = newBlockElement;
9472 // MOZ_KnownLive because 'aArrayOfContents' is guaranteed to keep it
9473 // alive.
9474 Result<MoveNodeResult, nsresult> moveNodeResult =
9475 MoveNodeToEndWithTransaction(MOZ_KnownLive(content),
9476 *newBlockElement);
9477 if (MOZ_UNLIKELY(moveNodeResult.isErr())) {
9478 NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed");
9479 return moveNodeResult.propagateErr();
9481 MoveNodeResult unwrappedMoveNodeResult = moveNodeResult.unwrap();
9482 unwrappedMoveNodeResult.MoveCaretPointTo(
9483 pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
9484 curBlock = std::move(newBlockElement);
9485 continue;
9488 if (HTMLEditUtils::IsInlineContent(content,
9489 BlockInlineCheck::UseHTMLDefaultStyle)) {
9490 // If content is inline, pull it into curBlock. Note: it's assumed that
9491 // consecutive inline nodes in aNodeArray are actually members of the
9492 // same block parent. This happens to be true now as a side effect of
9493 // how aNodeArray is constructed, but some additional logic should be
9494 // added here if that should change
9496 // If content is a non editable, drop it if we are going to <pre>.
9497 if (&aNewFormatTagName == nsGkAtoms::pre &&
9498 !EditorUtils::IsEditableContent(content, EditorType::HTML)) {
9499 // Do nothing to this block
9500 continue;
9503 // If no curBlock, make one
9504 if (!curBlock) {
9505 Result<CreateElementResult, nsresult> createNewBlockElementResult =
9506 InsertElementWithSplittingAncestorsWithTransaction(
9507 aNewFormatTagName, atContent, BRElementNextToSplitPoint::Keep,
9508 aEditingHost);
9509 if (MOZ_UNLIKELY(createNewBlockElementResult.isErr())) {
9510 NS_WARNING(nsPrintfCString("HTMLEditor::"
9511 "InsertElementWithSplittingAncestorsWith"
9512 "Transaction(%s) failed",
9513 nsAtomCString(&aNewFormatTagName).get())
9514 .get());
9515 return createNewBlockElementResult;
9517 CreateElementResult unwrappedCreateNewBlockElementResult =
9518 createNewBlockElementResult.unwrap();
9519 unwrappedCreateNewBlockElementResult.MoveCaretPointTo(
9520 pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
9521 MOZ_ASSERT(unwrappedCreateNewBlockElementResult.GetNewNode());
9522 blockElementToPutCaret =
9523 unwrappedCreateNewBlockElementResult.GetNewNode();
9524 curBlock = unwrappedCreateNewBlockElementResult.UnwrapNewNode();
9526 // Update container of content.
9527 atContent.Set(content);
9528 if (NS_WARN_IF(!atContent.IsSet())) {
9529 // This is possible due to mutation events, let's not assert
9530 return Err(NS_ERROR_UNEXPECTED);
9532 } else if (pendingBRElementToMoveCurBlock) {
9533 Result<CreateElementResult, nsresult> insertBRElementResult =
9534 InsertNodeWithTransaction<Element>(
9535 *pendingBRElementToMoveCurBlock,
9536 EditorDOMPoint::AtEndOf(*curBlock));
9537 if (MOZ_UNLIKELY(insertBRElementResult.isErr())) {
9538 NS_WARNING("EditorBase::InsertNodeWithTransaction<Element>() failed");
9539 return insertBRElementResult.propagateErr();
9541 insertBRElementResult.inspect().IgnoreCaretPointSuggestion();
9542 pendingBRElementToMoveCurBlock = nullptr;
9545 // XXX If content is a br, replace it with a return if going to <pre>
9547 // This is a continuation of some inline nodes that belong together in
9548 // the same block item. Use curBlock.
9550 // MOZ_KnownLive because 'aArrayOfContents' is guaranteed to keep it
9551 // alive. We could try to make that a rvalue ref and create a const array
9552 // on the stack here, but callers are passing in auto arrays, and we don't
9553 // want to introduce copies..
9554 Result<MoveNodeResult, nsresult> moveNodeResult =
9555 MoveNodeToEndWithTransaction(MOZ_KnownLive(content), *curBlock);
9556 if (MOZ_UNLIKELY(moveNodeResult.isErr())) {
9557 NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed");
9558 return moveNodeResult.propagateErr();
9560 MoveNodeResult unwrappedMoveNodeResult = moveNodeResult.unwrap();
9561 unwrappedMoveNodeResult.MoveCaretPointTo(
9562 pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
9565 return blockElementToPutCaret
9566 ? CreateElementResult(std::move(blockElementToPutCaret),
9567 std::move(pointToPutCaret))
9568 : CreateElementResult::NotHandled(std::move(pointToPutCaret));
9571 Result<SplitNodeResult, nsresult>
9572 HTMLEditor::MaybeSplitAncestorsForInsertWithTransaction(
9573 const nsAtom& aTag, const EditorDOMPoint& aStartOfDeepestRightNode,
9574 const Element& aEditingHost) {
9575 MOZ_ASSERT(IsEditActionDataAvailable());
9577 if (NS_WARN_IF(!aEditingHost.IsInComposedDoc())) {
9578 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
9581 if (NS_WARN_IF(!aStartOfDeepestRightNode.IsSet())) {
9582 return Err(NS_ERROR_INVALID_ARG);
9584 MOZ_ASSERT(aStartOfDeepestRightNode.IsSetAndValid());
9586 // The point must be descendant of editing host.
9587 // XXX Isn't it a valid case if it points a direct child of aEditingHost?
9588 if (NS_WARN_IF(
9589 !aStartOfDeepestRightNode.GetContainer()->IsInclusiveDescendantOf(
9590 &aEditingHost))) {
9591 return Err(NS_ERROR_INVALID_ARG);
9594 // Look for a node that can legally contain the tag.
9595 const EditorDOMPoint pointToInsert =
9596 HTMLEditUtils::GetInsertionPointInInclusiveAncestor(
9597 aTag, aStartOfDeepestRightNode, &aEditingHost);
9598 if (MOZ_UNLIKELY(!pointToInsert.IsSet())) {
9599 NS_WARNING(
9600 "HTMLEditor::MaybeSplitAncestorsForInsertWithTransaction() reached "
9601 "editing host");
9602 return Err(NS_ERROR_FAILURE);
9604 // If the point itself can contain the tag, we don't need to split any
9605 // ancestor nodes. In this case, we should return the given split point
9606 // as is.
9607 if (pointToInsert.GetContainer() == aStartOfDeepestRightNode.GetContainer()) {
9608 return SplitNodeResult::NotHandled(aStartOfDeepestRightNode);
9611 Result<SplitNodeResult, nsresult> splitNodeResult =
9612 SplitNodeDeepWithTransaction(MOZ_KnownLive(*pointToInsert.GetChild()),
9613 aStartOfDeepestRightNode,
9614 SplitAtEdges::eAllowToCreateEmptyContainer);
9615 NS_WARNING_ASSERTION(splitNodeResult.isOk(),
9616 "HTMLEditor::SplitNodeDeepWithTransaction(SplitAtEdges::"
9617 "eAllowToCreateEmptyContainer) failed");
9618 return splitNodeResult;
9621 Result<CreateElementResult, nsresult>
9622 HTMLEditor::InsertElementWithSplittingAncestorsWithTransaction(
9623 const nsAtom& aTagName, const EditorDOMPoint& aPointToInsert,
9624 BRElementNextToSplitPoint aBRElementNextToSplitPoint,
9625 const Element& aEditingHost,
9626 const InitializeInsertingElement& aInitializer) {
9627 MOZ_ASSERT(aPointToInsert.IsSetAndValid());
9629 const nsCOMPtr<nsIContent> childAtPointToInsert = aPointToInsert.GetChild();
9630 Result<SplitNodeResult, nsresult> splitNodeResult =
9631 MaybeSplitAncestorsForInsertWithTransaction(aTagName, aPointToInsert,
9632 aEditingHost);
9633 if (MOZ_UNLIKELY(splitNodeResult.isErr())) {
9634 NS_WARNING(
9635 "HTMLEditor::MaybeSplitAncestorsForInsertWithTransaction() failed");
9636 return splitNodeResult.propagateErr();
9638 SplitNodeResult unwrappedSplitNodeResult = splitNodeResult.unwrap();
9639 DebugOnly<bool> wasCaretPositionSuggestedAtSplit =
9640 unwrappedSplitNodeResult.HasCaretPointSuggestion();
9641 // We'll update selection below, and nobody touches selection until then.
9642 // Therefore, we don't need to touch selection here.
9643 unwrappedSplitNodeResult.IgnoreCaretPointSuggestion();
9645 // If current handling node has been moved from the container by a
9646 // mutation event listener when we need to do something more for it,
9647 // we should stop handling this action since we cannot handle each
9648 // edge case.
9649 if (childAtPointToInsert &&
9650 NS_WARN_IF(!childAtPointToInsert->IsInclusiveDescendantOf(
9651 unwrappedSplitNodeResult.DidSplit()
9652 ? unwrappedSplitNodeResult.GetNextContent()
9653 : aPointToInsert.GetContainer()))) {
9654 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
9657 auto splitPoint = unwrappedSplitNodeResult.AtSplitPoint<EditorDOMPoint>();
9658 if (aBRElementNextToSplitPoint == BRElementNextToSplitPoint::Delete) {
9659 // Consume a trailing br, if any. This is to keep an alignment from
9660 // creating extra lines, if possible.
9661 if (nsCOMPtr<nsIContent> maybeBRContent = HTMLEditUtils::GetNextContent(
9662 splitPoint,
9663 {WalkTreeOption::IgnoreNonEditableNode,
9664 WalkTreeOption::StopAtBlockBoundary},
9665 BlockInlineCheck::UseComputedDisplayOutsideStyle, &aEditingHost)) {
9666 if (maybeBRContent->IsHTMLElement(nsGkAtoms::br) &&
9667 splitPoint.GetChild()) {
9668 // Making use of html structure... if next node after where we are
9669 // putting our div is not a block, then the br we found is in same
9670 // block we are, so it's safe to consume it.
9671 if (nsIContent* nextEditableSibling = HTMLEditUtils::GetNextSibling(
9672 *splitPoint.GetChild(),
9673 {WalkTreeOption::IgnoreNonEditableNode})) {
9674 if (!HTMLEditUtils::IsBlockElement(
9675 *nextEditableSibling,
9676 BlockInlineCheck::UseComputedDisplayOutsideStyle)) {
9677 AutoEditorDOMPointChildInvalidator lockOffset(splitPoint);
9678 nsresult rv = DeleteNodeWithTransaction(*maybeBRContent);
9679 if (NS_FAILED(rv)) {
9680 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
9681 return Err(rv);
9689 Result<CreateElementResult, nsresult> createNewElementResult =
9690 CreateAndInsertElement(WithTransaction::Yes, aTagName, splitPoint,
9691 aInitializer);
9692 if (MOZ_UNLIKELY(createNewElementResult.isErr())) {
9693 NS_WARNING(
9694 "HTMLEditor::CreateAndInsertElement(WithTransaction::Yes) failed");
9695 return createNewElementResult;
9697 MOZ_ASSERT_IF(wasCaretPositionSuggestedAtSplit,
9698 createNewElementResult.inspect().HasCaretPointSuggestion());
9699 MOZ_ASSERT(createNewElementResult.inspect().GetNewNode());
9701 // If the new block element was moved to different element or removed by
9702 // the web app via mutation event listener, we should stop handling this
9703 // action since we cannot handle each of a lot of edge cases.
9704 if (NS_WARN_IF(
9705 createNewElementResult.inspect().GetNewNode()->GetParentNode() !=
9706 splitPoint.GetContainer())) {
9707 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
9710 return createNewElementResult;
9713 nsresult HTMLEditor::JoinNearestEditableNodesWithTransaction(
9714 nsIContent& aNodeLeft, nsIContent& aNodeRight,
9715 EditorDOMPoint* aNewFirstChildOfRightNode) {
9716 MOZ_ASSERT(IsEditActionDataAvailable());
9717 MOZ_ASSERT(aNewFirstChildOfRightNode);
9719 // Caller responsible for left and right node being the same type
9720 if (NS_WARN_IF(!aNodeLeft.GetParentNode())) {
9721 return NS_ERROR_FAILURE;
9723 // If they don't have the same parent, first move the right node to after
9724 // the left one
9725 if (aNodeLeft.GetParentNode() != aNodeRight.GetParentNode()) {
9726 Result<MoveNodeResult, nsresult> moveNodeResult =
9727 MoveNodeWithTransaction(aNodeRight, EditorDOMPoint(&aNodeLeft));
9728 if (MOZ_UNLIKELY(moveNodeResult.isErr())) {
9729 NS_WARNING("HTMLEditor::MoveNodeWithTransaction() failed");
9730 return moveNodeResult.unwrapErr();
9732 nsresult rv = moveNodeResult.inspect().SuggestCaretPointTo(
9733 *this, {SuggestCaret::OnlyIfHasSuggestion,
9734 SuggestCaret::OnlyIfTransactionsAllowedToDoIt,
9735 SuggestCaret::AndIgnoreTrivialError});
9736 if (NS_FAILED(rv)) {
9737 NS_WARNING("MoveNodeResult::SuggestCaretPointTo() failed");
9738 return rv;
9740 NS_WARNING_ASSERTION(
9741 rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
9742 "MoveNodeResult::SuggestCaretPointTo() failed, but ignored");
9745 // Separate join rules for differing blocks
9746 if (HTMLEditUtils::IsAnyListElement(&aNodeLeft) || aNodeLeft.IsText()) {
9747 // For lists, merge shallow (wouldn't want to combine list items)
9748 Result<JoinNodesResult, nsresult> joinNodesResult =
9749 JoinNodesWithTransaction(aNodeLeft, aNodeRight);
9750 if (MOZ_UNLIKELY(joinNodesResult.isErr())) {
9751 NS_WARNING("HTMLEditor::JoinNodesWithTransaction failed");
9752 return joinNodesResult.unwrapErr();
9754 *aNewFirstChildOfRightNode =
9755 joinNodesResult.inspect().AtJoinedPoint<EditorDOMPoint>();
9756 return NS_OK;
9759 // Remember the last left child, and first right child
9760 nsCOMPtr<nsIContent> lastEditableChildOfLeftContent =
9761 HTMLEditUtils::GetLastChild(aNodeLeft,
9762 {WalkTreeOption::IgnoreNonEditableNode});
9763 if (MOZ_UNLIKELY(NS_WARN_IF(!lastEditableChildOfLeftContent))) {
9764 return NS_ERROR_FAILURE;
9767 nsCOMPtr<nsIContent> firstEditableChildOfRightContent =
9768 HTMLEditUtils::GetFirstChild(aNodeRight,
9769 {WalkTreeOption::IgnoreNonEditableNode});
9770 if (NS_WARN_IF(!firstEditableChildOfRightContent)) {
9771 return NS_ERROR_FAILURE;
9774 // For list items, divs, etc., merge smart
9775 Result<JoinNodesResult, nsresult> joinNodesResult =
9776 JoinNodesWithTransaction(aNodeLeft, aNodeRight);
9777 if (MOZ_UNLIKELY(joinNodesResult.isErr())) {
9778 NS_WARNING("HTMLEditor::JoinNodesWithTransaction() failed");
9779 return joinNodesResult.unwrapErr();
9782 if ((lastEditableChildOfLeftContent->IsText() ||
9783 lastEditableChildOfLeftContent->IsElement()) &&
9784 HTMLEditUtils::CanContentsBeJoined(*lastEditableChildOfLeftContent,
9785 *firstEditableChildOfRightContent)) {
9786 nsresult rv = JoinNearestEditableNodesWithTransaction(
9787 *lastEditableChildOfLeftContent, *firstEditableChildOfRightContent,
9788 aNewFirstChildOfRightNode);
9789 NS_WARNING_ASSERTION(
9790 NS_SUCCEEDED(rv),
9791 "HTMLEditor::JoinNearestEditableNodesWithTransaction() failed");
9792 return rv;
9794 *aNewFirstChildOfRightNode =
9795 joinNodesResult.inspect().AtJoinedPoint<EditorDOMPoint>();
9796 return NS_OK;
9799 Element* HTMLEditor::GetMostDistantAncestorMailCiteElement(
9800 const nsINode& aNode) const {
9801 Element* mailCiteElement = nullptr;
9802 const bool isPlaintextEditor = IsPlaintextMailComposer();
9803 for (Element* element : aNode.InclusiveAncestorsOfType<Element>()) {
9804 if ((isPlaintextEditor && element->IsHTMLElement(nsGkAtoms::pre)) ||
9805 HTMLEditUtils::IsMailCite(*element)) {
9806 mailCiteElement = element;
9807 continue;
9809 if (element->IsHTMLElement(nsGkAtoms::body)) {
9810 break;
9813 return mailCiteElement;
9816 nsresult HTMLEditor::CacheInlineStyles(Element& Element) {
9817 MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable());
9819 nsresult rv = GetInlineStyles(
9820 Element, *TopLevelEditSubActionDataRef().mCachedPendingStyles);
9821 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
9822 "HTMLEditor::GetInlineStyles() failed");
9823 return rv;
9826 nsresult HTMLEditor::GetInlineStyles(
9827 Element& aElement, AutoPendingStyleCacheArray& aPendingStyleCacheArray) {
9828 MOZ_ASSERT(IsEditActionDataAvailable());
9829 MOZ_ASSERT(aPendingStyleCacheArray.IsEmpty());
9831 if (!IsCSSEnabled()) {
9832 // In the HTML styling mode, we should preserve the order of inline styles
9833 // specified with HTML elements, then, we can keep same order as original
9834 // one when we create new elements to apply the styles at new place.
9835 // XXX Currently, we don't preserve all inline parents, therefore, we cannot
9836 // restore all inline elements as-is. Perhaps, we should store all
9837 // inline elements with more details (e.g., all attributes), and store
9838 // same elements. For example, web apps may give style as:
9839 // em {
9840 // font-style: italic;
9841 // }
9842 // em em {
9843 // font-style: normal;
9844 // font-weight: bold;
9845 // }
9846 // but we cannot restore the style as-is.
9847 nsString value;
9848 const bool givenElementIsEditable =
9849 HTMLEditUtils::IsSimplyEditableNode(aElement);
9850 auto NeedToAppend = [&](nsStaticAtom& aTagName, nsStaticAtom* aAttribute) {
9851 if (mPendingStylesToApplyToNewContent->GetStyleState(
9852 aTagName, aAttribute) != PendingStyleState::NotUpdated) {
9853 return false; // The style has already been changed.
9855 if (aPendingStyleCacheArray.Contains(aTagName, aAttribute)) {
9856 return false; // Already preserved
9858 return true;
9860 for (Element* const inclusiveAncestor :
9861 aElement.InclusiveAncestorsOfType<Element>()) {
9862 if (HTMLEditUtils::IsBlockElement(
9863 *inclusiveAncestor,
9864 BlockInlineCheck::UseComputedDisplayOutsideStyle) ||
9865 (givenElementIsEditable &&
9866 !HTMLEditUtils::IsSimplyEditableNode(*inclusiveAncestor))) {
9867 break;
9869 if (inclusiveAncestor->IsAnyOfHTMLElements(
9870 nsGkAtoms::b, nsGkAtoms::i, nsGkAtoms::u, nsGkAtoms::s,
9871 nsGkAtoms::strike, nsGkAtoms::tt, nsGkAtoms::em,
9872 nsGkAtoms::strong, nsGkAtoms::dfn, nsGkAtoms::code,
9873 nsGkAtoms::samp, nsGkAtoms::var, nsGkAtoms::cite, nsGkAtoms::abbr,
9874 nsGkAtoms::acronym, nsGkAtoms::sub, nsGkAtoms::sup)) {
9875 nsStaticAtom& tagName = const_cast<nsStaticAtom&>(
9876 *inclusiveAncestor->NodeInfo()->NameAtom()->AsStatic());
9877 if (NeedToAppend(tagName, nullptr)) {
9878 aPendingStyleCacheArray.AppendElement(
9879 PendingStyleCache(tagName, nullptr, EmptyString()));
9881 continue;
9883 if (inclusiveAncestor->IsHTMLElement(nsGkAtoms::font)) {
9884 if (NeedToAppend(*nsGkAtoms::font, nsGkAtoms::face)) {
9885 inclusiveAncestor->GetAttr(nsGkAtoms::face, value);
9886 if (!value.IsEmpty()) {
9887 aPendingStyleCacheArray.AppendElement(
9888 PendingStyleCache(*nsGkAtoms::font, nsGkAtoms::face, value));
9889 value.Truncate();
9892 if (NeedToAppend(*nsGkAtoms::font, nsGkAtoms::size)) {
9893 inclusiveAncestor->GetAttr(nsGkAtoms::size, value);
9894 if (!value.IsEmpty()) {
9895 aPendingStyleCacheArray.AppendElement(
9896 PendingStyleCache(*nsGkAtoms::font, nsGkAtoms::size, value));
9897 value.Truncate();
9900 if (NeedToAppend(*nsGkAtoms::font, nsGkAtoms::color)) {
9901 inclusiveAncestor->GetAttr(nsGkAtoms::color, value);
9902 if (!value.IsEmpty()) {
9903 aPendingStyleCacheArray.AppendElement(
9904 PendingStyleCache(*nsGkAtoms::font, nsGkAtoms::color, value));
9905 value.Truncate();
9908 continue;
9911 return NS_OK;
9914 for (nsStaticAtom* property : {nsGkAtoms::b,
9915 nsGkAtoms::i,
9916 nsGkAtoms::u,
9917 nsGkAtoms::s,
9918 nsGkAtoms::strike,
9919 nsGkAtoms::face,
9920 nsGkAtoms::size,
9921 nsGkAtoms::color,
9922 nsGkAtoms::tt,
9923 nsGkAtoms::em,
9924 nsGkAtoms::strong,
9925 nsGkAtoms::dfn,
9926 nsGkAtoms::code,
9927 nsGkAtoms::samp,
9928 nsGkAtoms::var,
9929 nsGkAtoms::cite,
9930 nsGkAtoms::abbr,
9931 nsGkAtoms::acronym,
9932 nsGkAtoms::backgroundColor,
9933 nsGkAtoms::sub,
9934 nsGkAtoms::sup}) {
9935 const EditorInlineStyle style =
9936 property == nsGkAtoms::face || property == nsGkAtoms::size ||
9937 property == nsGkAtoms::color
9938 ? EditorInlineStyle(*nsGkAtoms::font, property)
9939 : EditorInlineStyle(*property);
9940 // If type-in state is set, don't intervene
9941 const PendingStyleState styleState =
9942 mPendingStylesToApplyToNewContent->GetStyleState(*style.mHTMLProperty,
9943 style.mAttribute);
9944 if (styleState != PendingStyleState::NotUpdated) {
9945 continue;
9947 bool isSet = false;
9948 nsString value; // Don't use nsAutoString here because it requires memcpy
9949 // at creating new PendingStyleCache instance.
9950 // Don't use CSS for <font size>, we don't support it usefully (bug 780035)
9951 if (property == nsGkAtoms::size) {
9952 isSet = HTMLEditUtils::IsInlineStyleSetByElement(aElement, style, nullptr,
9953 &value);
9954 } else if (style.IsCSSSettable(aElement)) {
9955 Result<bool, nsresult> isComputedCSSEquivalentToStyleOrError =
9956 CSSEditUtils::IsComputedCSSEquivalentTo(*this, aElement, style,
9957 value);
9958 if (MOZ_UNLIKELY(isComputedCSSEquivalentToStyleOrError.isErr())) {
9959 NS_WARNING("CSSEditUtils::IsComputedCSSEquivalentTo() failed");
9960 return isComputedCSSEquivalentToStyleOrError.unwrapErr();
9962 isSet = isComputedCSSEquivalentToStyleOrError.unwrap();
9964 if (isSet) {
9965 aPendingStyleCacheArray.AppendElement(
9966 style.ToPendingStyleCache(std::move(value)));
9969 return NS_OK;
9972 nsresult HTMLEditor::ReapplyCachedStyles() {
9973 MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable());
9975 // The idea here is to examine our cached list of styles and see if any have
9976 // been removed. If so, add typeinstate for them, so that they will be
9977 // reinserted when new content is added.
9979 if (TopLevelEditSubActionDataRef().mCachedPendingStyles->IsEmpty() ||
9980 !SelectionRef().RangeCount()) {
9981 return NS_OK;
9984 // remember if we are in css mode
9985 const bool useCSS = IsCSSEnabled();
9987 const RangeBoundary& atStartOfSelection =
9988 SelectionRef().GetRangeAt(0)->StartRef();
9989 const RefPtr<Element> startContainerElement =
9990 atStartOfSelection.Container() &&
9991 atStartOfSelection.Container()->IsContent()
9992 ? atStartOfSelection.Container()->GetAsElementOrParentElement()
9993 : nullptr;
9994 if (NS_WARN_IF(!startContainerElement)) {
9995 return NS_OK;
9998 AutoPendingStyleCacheArray styleCacheArrayAtInsertionPoint;
9999 nsresult rv =
10000 GetInlineStyles(*startContainerElement, styleCacheArrayAtInsertionPoint);
10001 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
10002 return NS_ERROR_EDITOR_DESTROYED;
10004 if (NS_FAILED(rv)) {
10005 NS_WARNING("HTMLEditor::GetInlineStyles() failed, but ignored");
10006 return NS_OK;
10009 for (PendingStyleCache& styleCacheBeforeEdit :
10010 Reversed(*TopLevelEditSubActionDataRef().mCachedPendingStyles)) {
10011 bool isFirst = false, isAny = false, isAll = false;
10012 nsAutoString currentValue;
10013 const EditorInlineStyle inlineStyle = styleCacheBeforeEdit.ToInlineStyle();
10014 if (useCSS && inlineStyle.IsCSSSettable(*startContainerElement)) {
10015 // check computed style first in css case
10016 // MOZ_KnownLive(styleCacheBeforeEdit.*) because they are nsStaticAtom
10017 // and its instances are alive until shutting down.
10018 Result<bool, nsresult> isComputedCSSEquivalentToStyleOrError =
10019 CSSEditUtils::IsComputedCSSEquivalentTo(*this, *startContainerElement,
10020 inlineStyle, currentValue);
10021 if (MOZ_UNLIKELY(isComputedCSSEquivalentToStyleOrError.isErr())) {
10022 NS_WARNING("CSSEditUtils::IsComputedCSSEquivalentTo() failed");
10023 return isComputedCSSEquivalentToStyleOrError.unwrapErr();
10025 isAny = isComputedCSSEquivalentToStyleOrError.unwrap();
10027 if (!isAny) {
10028 // then check typeinstate and html style
10029 nsresult rv = GetInlinePropertyBase(
10030 inlineStyle, &styleCacheBeforeEdit.AttributeValueOrCSSValueRef(),
10031 &isFirst, &isAny, &isAll, &currentValue);
10032 if (NS_FAILED(rv)) {
10033 NS_WARNING("HTMLEditor::GetInlinePropertyBase() failed");
10034 return rv;
10037 // This style has disappeared through deletion. Let's add the styles to
10038 // mPendingStylesToApplyToNewContent when same style isn't applied to the
10039 // node already.
10040 if (isAny &&
10041 !IsPendingStyleCachePreservingSubAction(GetTopLevelEditSubAction())) {
10042 continue;
10044 AutoPendingStyleCacheArray::index_type index =
10045 styleCacheArrayAtInsertionPoint.IndexOf(
10046 styleCacheBeforeEdit.TagRef(), styleCacheBeforeEdit.GetAttribute());
10047 if (index == AutoPendingStyleCacheArray::NoIndex ||
10048 styleCacheBeforeEdit.AttributeValueOrCSSValueRef() !=
10049 styleCacheArrayAtInsertionPoint.ElementAt(index)
10050 .AttributeValueOrCSSValueRef()) {
10051 mPendingStylesToApplyToNewContent->PreserveStyle(styleCacheBeforeEdit);
10054 return NS_OK;
10057 nsresult HTMLEditor::InsertBRElementToEmptyListItemsAndTableCellsInRange(
10058 const RawRangeBoundary& aStartRef, const RawRangeBoundary& aEndRef) {
10059 MOZ_ASSERT(IsEditActionDataAvailable());
10061 AutoTArray<OwningNonNull<Element>, 64> arrayOfEmptyElements;
10062 DOMIterator iter;
10063 if (NS_FAILED(iter.Init(aStartRef, aEndRef))) {
10064 NS_WARNING("DOMIterator::Init() failed");
10065 return NS_ERROR_FAILURE;
10067 iter.AppendNodesToArray(
10068 +[](nsINode& aNode, void* aSelf) {
10069 MOZ_ASSERT(Element::FromNode(&aNode));
10070 MOZ_ASSERT(aSelf);
10071 Element* element = aNode.AsElement();
10072 if (!EditorUtils::IsEditableContent(*element, EditorType::HTML) ||
10073 (!HTMLEditUtils::IsListItem(element) &&
10074 !HTMLEditUtils::IsTableCellOrCaption(*element))) {
10075 return false;
10077 return HTMLEditUtils::IsEmptyNode(
10078 *element, {EmptyCheckOption::TreatSingleBRElementAsVisible,
10079 EmptyCheckOption::TreatNonEditableContentAsInvisible});
10081 arrayOfEmptyElements, this);
10083 // Put padding <br> elements for empty <li> and <td>.
10084 EditorDOMPoint pointToPutCaret;
10085 for (auto& emptyElement : arrayOfEmptyElements) {
10086 // Need to put br at END of node. It may have empty containers in it and
10087 // still pass the "IsEmptyNode" test, and we want the br's to be after
10088 // them. Also, we want the br to be after the selection if the selection
10089 // is in this node.
10090 EditorDOMPoint endOfNode(EditorDOMPoint::AtEndOf(emptyElement));
10091 Result<CreateElementResult, nsresult> insertPaddingBRElementResult =
10092 InsertPaddingBRElementForEmptyLastLineWithTransaction(endOfNode);
10093 if (MOZ_UNLIKELY(insertPaddingBRElementResult.isErr())) {
10094 NS_WARNING(
10095 "HTMLEditor::InsertPaddingBRElementForEmptyLastLineWithTransaction() "
10096 "failed");
10097 return insertPaddingBRElementResult.unwrapErr();
10099 CreateElementResult unwrappedInsertPaddingBRElementResult =
10100 insertPaddingBRElementResult.unwrap();
10101 unwrappedInsertPaddingBRElementResult.MoveCaretPointTo(
10102 pointToPutCaret, *this,
10103 {SuggestCaret::OnlyIfHasSuggestion,
10104 SuggestCaret::OnlyIfTransactionsAllowedToDoIt});
10106 if (pointToPutCaret.IsSet()) {
10107 nsresult rv = CollapseSelectionTo(pointToPutCaret);
10108 if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) {
10109 NS_WARNING(
10110 "EditorBase::CollapseSelectionTo() caused destroying the editor");
10111 return NS_ERROR_EDITOR_DESTROYED;
10113 NS_WARNING_ASSERTION(
10114 NS_SUCCEEDED(rv),
10115 "EditorBase::CollapseSelectionTo() failed, but ignored");
10117 return NS_OK;
10120 void HTMLEditor::SetSelectionInterlinePosition() {
10121 MOZ_ASSERT(IsEditActionDataAvailable());
10122 MOZ_ASSERT(SelectionRef().IsCollapsed());
10124 // Get the (collapsed) selection location
10125 const nsRange* firstRange = SelectionRef().GetRangeAt(0);
10126 if (NS_WARN_IF(!firstRange)) {
10127 return;
10130 EditorDOMPoint atCaret(firstRange->StartRef());
10131 if (NS_WARN_IF(!atCaret.IsSet())) {
10132 return;
10134 MOZ_ASSERT(atCaret.IsSetAndValid());
10136 // First, let's check to see if we are after a `<br>`. We take care of this
10137 // special-case first so that we don't accidentally fall through into one of
10138 // the other conditionals.
10139 // XXX Although I don't understand "interline position", if caret is
10140 // immediately after non-editable contents, but previous editable
10141 // content is `<br>`, does this do right thing?
10142 if (Element* editingHost = ComputeEditingHost()) {
10143 if (nsIContent* previousEditableContentInBlock =
10144 HTMLEditUtils::GetPreviousContent(
10145 atCaret,
10146 {WalkTreeOption::IgnoreNonEditableNode,
10147 WalkTreeOption::StopAtBlockBoundary},
10148 BlockInlineCheck::UseComputedDisplayStyle, editingHost)) {
10149 if (previousEditableContentInBlock->IsHTMLElement(nsGkAtoms::br)) {
10150 DebugOnly<nsresult> rvIgnored = SelectionRef().SetInterlinePosition(
10151 InterlinePosition::StartOfNextLine);
10152 NS_WARNING_ASSERTION(
10153 NS_SUCCEEDED(rvIgnored),
10154 "Selection::SetInterlinePosition(InterlinePosition::"
10155 "StartOfNextLine) failed, but ignored");
10156 return;
10161 if (!atCaret.GetChild()) {
10162 return;
10165 // If caret is immediately after a block, set interline position to "right".
10166 // XXX Although I don't understand "interline position", if caret is
10167 // immediately after non-editable contents, but previous editable
10168 // content is a block, does this do right thing?
10169 if (nsIContent* previousEditableContentInBlockAtCaret =
10170 HTMLEditUtils::GetPreviousSibling(
10171 *atCaret.GetChild(), {WalkTreeOption::IgnoreNonEditableNode})) {
10172 if (HTMLEditUtils::IsBlockElement(
10173 *previousEditableContentInBlockAtCaret,
10174 BlockInlineCheck::UseComputedDisplayStyle)) {
10175 DebugOnly<nsresult> rvIgnored = SelectionRef().SetInterlinePosition(
10176 InterlinePosition::StartOfNextLine);
10177 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
10178 "Selection::SetInterlinePosition(InterlinePosition::"
10179 "StartOfNextLine) failed, but ignored");
10180 return;
10184 // If caret is immediately before a block, set interline position to "left".
10185 // XXX Although I don't understand "interline position", if caret is
10186 // immediately before non-editable contents, but next editable
10187 // content is a block, does this do right thing?
10188 if (nsIContent* nextEditableContentInBlockAtCaret =
10189 HTMLEditUtils::GetNextSibling(
10190 *atCaret.GetChild(), {WalkTreeOption::IgnoreNonEditableNode})) {
10191 if (HTMLEditUtils::IsBlockElement(
10192 *nextEditableContentInBlockAtCaret,
10193 BlockInlineCheck::UseComputedDisplayStyle)) {
10194 DebugOnly<nsresult> rvIgnored =
10195 SelectionRef().SetInterlinePosition(InterlinePosition::EndOfLine);
10196 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
10197 "Selection::SetInterlinePosition(InterlinePosition::"
10198 "EndOfLine) failed, but ignored");
10203 nsresult HTMLEditor::AdjustCaretPositionAndEnsurePaddingBRElement(
10204 nsIEditor::EDirection aDirectionAndAmount) {
10205 MOZ_ASSERT(IsEditActionDataAvailable());
10206 MOZ_ASSERT(SelectionRef().IsCollapsed());
10208 auto point = GetFirstSelectionStartPoint<EditorDOMPoint>();
10209 if (NS_WARN_IF(!point.IsInContentNode())) {
10210 return NS_ERROR_FAILURE;
10213 // If selection start is not editable, climb up the tree until editable one.
10214 while (!EditorUtils::IsEditableContent(*point.ContainerAs<nsIContent>(),
10215 EditorType::HTML)) {
10216 point.Set(point.GetContainer());
10217 if (NS_WARN_IF(!point.IsInContentNode())) {
10218 return NS_ERROR_FAILURE;
10222 // If caret is in empty block element, we need to insert a `<br>` element
10223 // because the block should have one-line height.
10224 // XXX Even if only a part of the block is editable, shouldn't we put
10225 // caret if the block element is now empty?
10226 if (Element* const editableBlockElement =
10227 HTMLEditUtils::GetInclusiveAncestorElement(
10228 *point.ContainerAs<nsIContent>(),
10229 HTMLEditUtils::ClosestEditableBlockElement,
10230 BlockInlineCheck::UseComputedDisplayStyle)) {
10231 if (editableBlockElement &&
10232 HTMLEditUtils::IsEmptyNode(
10233 *editableBlockElement,
10234 {EmptyCheckOption::TreatSingleBRElementAsVisible}) &&
10235 HTMLEditUtils::CanNodeContain(*point.GetContainer(), *nsGkAtoms::br)) {
10236 Element* bodyOrDocumentElement = GetRoot();
10237 if (NS_WARN_IF(!bodyOrDocumentElement)) {
10238 return NS_ERROR_FAILURE;
10240 if (point.GetContainer() == bodyOrDocumentElement) {
10241 // Our root node is completely empty. Don't add a <br> here.
10242 // AfterEditInner() will add one for us when it calls
10243 // EditorBase::MaybeCreatePaddingBRElementForEmptyEditor().
10244 // XXX This kind of dependency between methods makes us spaghetti.
10245 // Let's handle it here later.
10246 // XXX This looks odd check. If active editing host is not a
10247 // `<body>`, what are we doing?
10248 return NS_OK;
10250 Result<CreateElementResult, nsresult> insertPaddingBRElementResult =
10251 InsertPaddingBRElementForEmptyLastLineWithTransaction(point);
10252 if (MOZ_UNLIKELY(insertPaddingBRElementResult.isErr())) {
10253 NS_WARNING(
10254 "HTMLEditor::InsertPaddingBRElementForEmptyLastLineWithTransaction("
10255 ") failed");
10256 return insertPaddingBRElementResult.unwrapErr();
10258 nsresult rv = insertPaddingBRElementResult.inspect().SuggestCaretPointTo(
10259 *this, {SuggestCaret::OnlyIfHasSuggestion,
10260 SuggestCaret::OnlyIfTransactionsAllowedToDoIt,
10261 SuggestCaret::AndIgnoreTrivialError});
10262 if (NS_FAILED(rv)) {
10263 NS_WARNING("CreateElementResult::SuggestCaretPointTo() failed");
10264 return rv;
10266 NS_WARNING_ASSERTION(
10267 rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
10268 "CreateElementResult::SuggestCaretPointTo() failed, but ignored");
10269 return NS_OK;
10273 // XXX Perhaps, we should do something if we're in a data node but not
10274 // a text node.
10275 if (point.IsInTextNode()) {
10276 return NS_OK;
10279 // Do we need to insert a padding <br> element for empty last line? We do
10280 // if we are:
10281 // 1) prior node is in same block where selection is AND
10282 // 2) prior node is a br AND
10283 // 3) that br is not visible
10284 RefPtr<Element> editingHost = ComputeEditingHost();
10285 if (!editingHost) {
10286 return NS_OK;
10289 if (nsCOMPtr<nsIContent> previousEditableContent =
10290 HTMLEditUtils::GetPreviousContent(
10291 point, {WalkTreeOption::IgnoreNonEditableNode},
10292 BlockInlineCheck::UseComputedDisplayStyle, editingHost)) {
10293 // If caret and previous editable content are in same block element
10294 // (even if it's a non-editable element), we should put a padding <br>
10295 // element at end of the block.
10296 const Element* const blockElementContainingCaret =
10297 HTMLEditUtils::GetInclusiveAncestorElement(
10298 *point.ContainerAs<nsIContent>(),
10299 HTMLEditUtils::ClosestBlockElement,
10300 BlockInlineCheck::UseComputedDisplayStyle);
10301 const Element* const blockElementContainingPreviousEditableContent =
10302 HTMLEditUtils::GetAncestorElement(
10303 *previousEditableContent, HTMLEditUtils::ClosestBlockElement,
10304 BlockInlineCheck::UseComputedDisplayStyle);
10305 // If previous editable content of caret is in same block and a `<br>`
10306 // element, we need to adjust interline position.
10307 if (blockElementContainingCaret &&
10308 blockElementContainingCaret ==
10309 blockElementContainingPreviousEditableContent &&
10310 point.ContainerAs<nsIContent>()->GetEditingHost() ==
10311 previousEditableContent->GetEditingHost() &&
10312 previousEditableContent &&
10313 previousEditableContent->IsHTMLElement(nsGkAtoms::br)) {
10314 // If it's an invisible `<br>` element, we need to insert a padding
10315 // `<br>` element for making empty line have one-line height.
10316 if (HTMLEditUtils::IsInvisibleBRElement(*previousEditableContent) &&
10317 !EditorUtils::IsPaddingBRElementForEmptyLastLine(
10318 *previousEditableContent)) {
10319 AutoEditorDOMPointChildInvalidator lockOffset(point);
10320 Result<CreateElementResult, nsresult> insertPaddingBRElementResult =
10321 InsertPaddingBRElementForEmptyLastLineWithTransaction(point);
10322 if (MOZ_UNLIKELY(insertPaddingBRElementResult.isErr())) {
10323 NS_WARNING(
10324 "HTMLEditor::"
10325 "InsertPaddingBRElementForEmptyLastLineWithTransaction() failed");
10326 return insertPaddingBRElementResult.unwrapErr();
10328 insertPaddingBRElementResult.inspect().IgnoreCaretPointSuggestion();
10329 nsresult rv = CollapseSelectionTo(EditorRawDOMPoint(
10330 insertPaddingBRElementResult.inspect().GetNewNode(),
10331 InterlinePosition::StartOfNextLine));
10332 if (NS_FAILED(rv)) {
10333 NS_WARNING("EditorBase::CollapseSelectionTo() failed");
10334 return rv;
10337 // If it's a visible `<br>` element and next editable content is a
10338 // padding `<br>` element, we need to set interline position.
10339 else if (nsIContent* nextEditableContentInBlock =
10340 HTMLEditUtils::GetNextContent(
10341 *previousEditableContent,
10342 {WalkTreeOption::IgnoreNonEditableNode,
10343 WalkTreeOption::StopAtBlockBoundary},
10344 BlockInlineCheck::UseComputedDisplayStyle,
10345 editingHost)) {
10346 if (EditorUtils::IsPaddingBRElementForEmptyLastLine(
10347 *nextEditableContentInBlock)) {
10348 // Make it stick to the padding `<br>` element so that it will be
10349 // on blank line.
10350 DebugOnly<nsresult> rvIgnored = SelectionRef().SetInterlinePosition(
10351 InterlinePosition::StartOfNextLine);
10352 NS_WARNING_ASSERTION(
10353 NS_SUCCEEDED(rvIgnored),
10354 "Selection::SetInterlinePosition(InterlinePosition::"
10355 "StartOfNextLine) failed, but ignored");
10361 // If previous editable content in same block is `<br>`, text node, `<img>`
10362 // or `<hr>`, current caret position is fine.
10363 if (nsIContent* previousEditableContentInBlock =
10364 HTMLEditUtils::GetPreviousContent(
10365 point,
10366 {WalkTreeOption::IgnoreNonEditableNode,
10367 WalkTreeOption::StopAtBlockBoundary},
10368 BlockInlineCheck::UseComputedDisplayStyle, editingHost)) {
10369 if (previousEditableContentInBlock->IsHTMLElement(nsGkAtoms::br) ||
10370 previousEditableContentInBlock->IsText() ||
10371 HTMLEditUtils::IsImage(previousEditableContentInBlock) ||
10372 previousEditableContentInBlock->IsHTMLElement(nsGkAtoms::hr)) {
10373 return NS_OK;
10377 // If next editable content in same block is `<br>`, text node, `<img>` or
10378 // `<hr>`, current caret position is fine.
10379 if (nsIContent* nextEditableContentInBlock = HTMLEditUtils::GetNextContent(
10380 point,
10381 {WalkTreeOption::IgnoreNonEditableNode,
10382 WalkTreeOption::StopAtBlockBoundary},
10383 BlockInlineCheck::UseComputedDisplayStyle, editingHost)) {
10384 if (nextEditableContentInBlock->IsText() ||
10385 nextEditableContentInBlock->IsAnyOfHTMLElements(
10386 nsGkAtoms::br, nsGkAtoms::img, nsGkAtoms::hr)) {
10387 return NS_OK;
10391 // Otherwise, look for a near editable content towards edit action direction.
10393 // If there is no editable content, keep current caret position.
10394 // XXX Why do we treat `nsIEditor::ePreviousWord` etc as forward direction?
10395 nsIContent* nearEditableContent = HTMLEditUtils::GetAdjacentContentToPutCaret(
10396 point,
10397 aDirectionAndAmount == nsIEditor::ePrevious ? WalkTreeDirection::Backward
10398 : WalkTreeDirection::Forward,
10399 *editingHost);
10400 if (!nearEditableContent) {
10401 return NS_OK;
10404 EditorRawDOMPoint pointToPutCaret =
10405 HTMLEditUtils::GetGoodCaretPointFor<EditorRawDOMPoint>(
10406 *nearEditableContent, aDirectionAndAmount);
10407 if (!pointToPutCaret.IsSet()) {
10408 NS_WARNING("HTMLEditUtils::GetGoodCaretPointFor() failed");
10409 return NS_ERROR_FAILURE;
10411 nsresult rv = CollapseSelectionTo(pointToPutCaret);
10412 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
10413 "EditorBase::CollapseSelectionTo() failed");
10414 return rv;
10417 nsresult HTMLEditor::RemoveEmptyNodesIn(const EditorDOMRange& aRange) {
10418 MOZ_ASSERT(IsEditActionDataAvailable());
10419 MOZ_ASSERT(aRange.IsPositioned());
10421 // Some general notes on the algorithm used here: the goal is to examine all
10422 // the nodes in aRange, and remove the empty ones. We do this by
10423 // using a content iterator to traverse all the nodes in the range, and
10424 // placing the empty nodes into an array. After finishing the iteration,
10425 // we delete the empty nodes in the array. (They cannot be deleted as we
10426 // find them because that would invalidate the iterator.)
10428 // Since checking to see if a node is empty can be costly for nodes with
10429 // many descendants, there are some optimizations made. I rely on the fact
10430 // that the iterator is post-order: it will visit children of a node before
10431 // visiting the parent node. So if I find that a child node is not empty, I
10432 // know that its parent is not empty without even checking. So I put the
10433 // parent on a "skipList" which is just a voidArray of nodes I can skip the
10434 // empty check on. If I encounter a node on the skiplist, i skip the
10435 // processing for that node and replace its slot in the skiplist with that
10436 // node's parent.
10438 // An interesting idea is to go ahead and regard parent nodes that are NOT
10439 // on the skiplist as being empty (without even doing the IsEmptyNode check)
10440 // on the theory that if they weren't empty, we would have encountered a
10441 // non-empty child earlier and thus put this parent node on the skiplist.
10443 // Unfortunately I can't use that strategy here, because the range may
10444 // include some children of a node while excluding others. Thus I could
10445 // find all the _examined_ children empty, but still not have an empty
10446 // parent.
10448 const RawRangeBoundary endOfRange = [&]() {
10449 // If the range is not collapsed and end of the range is start of a
10450 // container, it means that the inclusive ancestor empty element may be
10451 // created by splitting the left nodes.
10452 if (aRange.Collapsed() || !aRange.IsInContentNodes() ||
10453 !aRange.EndRef().IsStartOfContainer()) {
10454 return aRange.EndRef().ToRawRangeBoundary();
10456 nsINode* const commonAncestor =
10457 nsContentUtils::GetClosestCommonInclusiveAncestor(
10458 aRange.StartRef().ContainerAs<nsIContent>(),
10459 aRange.EndRef().ContainerAs<nsIContent>());
10460 if (!commonAncestor) {
10461 return aRange.EndRef().ToRawRangeBoundary();
10463 nsIContent* maybeRightContent = nullptr;
10464 for (nsIContent* content : aRange.EndRef()
10465 .ContainerAs<nsIContent>()
10466 ->InclusiveAncestorsOfType<nsIContent>()) {
10467 if (!HTMLEditUtils::IsSimplyEditableNode(*content) ||
10468 content == commonAncestor) {
10469 break;
10471 if (aRange.StartRef().ContainerAs<nsIContent>() == content) {
10472 break;
10474 EmptyCheckOptions options = {
10475 EmptyCheckOption::TreatListItemAsVisible,
10476 EmptyCheckOption::TreatTableCellAsVisible,
10477 EmptyCheckOption::TreatNonEditableContentAsInvisible};
10478 if (!HTMLEditUtils::IsBlockElement(
10479 *content, BlockInlineCheck::UseComputedDisplayStyle)) {
10480 options += EmptyCheckOption::TreatSingleBRElementAsVisible;
10482 if (!HTMLEditUtils::IsEmptyNode(*content, options)) {
10483 break;
10485 maybeRightContent = content;
10487 if (!maybeRightContent) {
10488 return aRange.EndRef().ToRawRangeBoundary();
10490 return EditorRawDOMPoint::After(*maybeRightContent).ToRawRangeBoundary();
10491 }();
10493 PostContentIterator postOrderIter;
10494 nsresult rv =
10495 postOrderIter.Init(aRange.StartRef().ToRawRangeBoundary(), endOfRange);
10496 if (NS_FAILED(rv)) {
10497 NS_WARNING("PostContentIterator::Init() failed");
10498 return rv;
10501 AutoTArray<OwningNonNull<nsIContent>, 64> arrayOfEmptyContents,
10502 arrayOfEmptyCites;
10504 // Collect empty nodes first.
10506 const bool isMailEditor = IsMailEditor();
10507 AutoTArray<OwningNonNull<nsIContent>, 64> knownNonEmptyContents;
10508 Maybe<AutoRangeArray> maybeSelectionRanges;
10509 for (; !postOrderIter.IsDone(); postOrderIter.Next()) {
10510 MOZ_ASSERT(postOrderIter.GetCurrentNode()->IsContent());
10512 nsIContent* content = postOrderIter.GetCurrentNode()->AsContent();
10513 nsIContent* parentContent = content->GetParent();
10515 size_t idx = knownNonEmptyContents.IndexOf(content);
10516 if (idx != decltype(knownNonEmptyContents)::NoIndex) {
10517 // This node is on our skip list. Skip processing for this node, and
10518 // replace its value in the skip list with the value of its parent
10519 if (parentContent) {
10520 knownNonEmptyContents[idx] = parentContent;
10522 continue;
10525 const bool isEmptyNode = [&]() {
10526 if (!content->IsElement()) {
10527 return false;
10529 const bool isMailCite =
10530 isMailEditor && HTMLEditUtils::IsMailCite(*content->AsElement());
10531 const bool isCandidate = [&]() {
10532 if (content->IsHTMLElement(nsGkAtoms::body)) {
10533 // Don't delete the body
10534 return false;
10536 if (isMailCite || content->IsHTMLElement(nsGkAtoms::a) ||
10537 HTMLEditUtils::IsInlineStyle(content) ||
10538 HTMLEditUtils::IsAnyListElement(content) ||
10539 content->IsHTMLElement(nsGkAtoms::div)) {
10540 // Only consider certain nodes to be empty for purposes of removal
10541 return true;
10543 if (HTMLEditUtils::IsFormatElementForFormatBlockCommand(*content) ||
10544 HTMLEditUtils::IsListItem(content) ||
10545 content->IsHTMLElement(nsGkAtoms::blockquote)) {
10546 // These node types are candidates if selection is not in them. If
10547 // it is one of these, don't delete if selection inside. This is so
10548 // we can create empty headings, etc., for the user to type into.
10549 if (maybeSelectionRanges.isNothing()) {
10550 maybeSelectionRanges.emplace(SelectionRef());
10552 return !maybeSelectionRanges
10553 ->IsAtLeastOneContainerOfRangeBoundariesInclusiveDescendantOf(
10554 *content);
10556 return false;
10557 }();
10559 if (!isCandidate) {
10560 return false;
10563 // We delete mailcites even if they have a solo br in them. Other
10564 // nodes we require to be empty.
10565 HTMLEditUtils::EmptyCheckOptions options{
10566 EmptyCheckOption::TreatListItemAsVisible,
10567 EmptyCheckOption::TreatTableCellAsVisible};
10568 if (!isMailCite) {
10569 options += EmptyCheckOption::TreatSingleBRElementAsVisible;
10570 } else {
10571 // XXX Maybe unnecessary to specify this.
10572 options += EmptyCheckOption::TreatNonEditableContentAsInvisible;
10574 if (!HTMLEditUtils::IsEmptyNode(*content, options)) {
10575 return false;
10578 if (isMailCite) {
10579 // mailcites go on a separate list from other empty nodes
10580 arrayOfEmptyCites.AppendElement(*content);
10582 // Don't delete non-editable nodes in this method because this is a
10583 // clean up method to remove unnecessary nodes of the result of
10584 // editing. So, we shouldn't delete non-editable nodes which were
10585 // there before editing. Additionally, if the element is some special
10586 // elements such as <body>, we shouldn't delete it.
10587 else if (HTMLEditUtils::IsSimplyEditableNode(*content) &&
10588 HTMLEditUtils::IsRemovableNode(*content)) {
10589 arrayOfEmptyContents.AppendElement(*content);
10591 return true;
10592 }();
10593 if (!isEmptyNode && parentContent) {
10594 knownNonEmptyContents.AppendElement(*parentContent);
10596 } // end of the for-loop iterating with postOrderIter
10599 // now delete the empty nodes
10600 for (OwningNonNull<nsIContent>& emptyContent : arrayOfEmptyContents) {
10601 // MOZ_KnownLive due to bug 1622253
10602 nsresult rv = DeleteNodeWithTransaction(MOZ_KnownLive(emptyContent));
10603 if (NS_FAILED(rv)) {
10604 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
10605 return rv;
10609 // Now delete the empty mailcites. This is a separate step because we want
10610 // to pull out any br's and preserve them.
10611 EditorDOMPoint pointToPutCaret;
10612 for (OwningNonNull<nsIContent>& emptyCite : arrayOfEmptyCites) {
10613 if (!HTMLEditUtils::IsEmptyNode(
10614 emptyCite,
10615 {EmptyCheckOption::TreatSingleBRElementAsVisible,
10616 EmptyCheckOption::TreatListItemAsVisible,
10617 EmptyCheckOption::TreatTableCellAsVisible,
10618 EmptyCheckOption::TreatNonEditableContentAsInvisible})) {
10619 // We are deleting a cite that has just a `<br>`. We want to delete cite,
10620 // but preserve `<br>`.
10621 Result<CreateElementResult, nsresult> insertBRElementResult =
10622 InsertBRElement(WithTransaction::Yes, EditorDOMPoint(emptyCite));
10623 if (MOZ_UNLIKELY(insertBRElementResult.isErr())) {
10624 NS_WARNING("HTMLEditor::InsertBRElement(WithTransaction::Yes) failed");
10625 return insertBRElementResult.unwrapErr();
10627 CreateElementResult unwrappedInsertBRElementResult =
10628 insertBRElementResult.unwrap();
10629 // XXX Is this intentional selection change?
10630 unwrappedInsertBRElementResult.MoveCaretPointTo(
10631 pointToPutCaret, *this,
10632 {SuggestCaret::OnlyIfHasSuggestion,
10633 SuggestCaret::OnlyIfTransactionsAllowedToDoIt});
10634 MOZ_ASSERT(unwrappedInsertBRElementResult.GetNewNode());
10636 // MOZ_KnownLive because 'arrayOfEmptyCites' is guaranteed to keep it alive.
10637 nsresult rv = DeleteNodeWithTransaction(MOZ_KnownLive(emptyCite));
10638 if (NS_FAILED(rv)) {
10639 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
10640 return rv;
10643 // XXX Is this intentional selection change?
10644 if (pointToPutCaret.IsSet()) {
10645 nsresult rv = CollapseSelectionTo(pointToPutCaret);
10646 if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) {
10647 NS_WARNING(
10648 "EditorBase::CollapseSelectionTo() caused destroying the editor");
10649 return NS_ERROR_EDITOR_DESTROYED;
10651 NS_WARNING_ASSERTION(
10652 NS_SUCCEEDED(rv),
10653 "EditorBase::CollapseSelectionTo() failed, but ignored");
10656 return NS_OK;
10659 nsresult HTMLEditor::LiftUpListItemElement(
10660 Element& aListItemElement,
10661 LiftUpFromAllParentListElements aLiftUpFromAllParentListElements) {
10662 MOZ_ASSERT(IsEditActionDataAvailable());
10664 if (!HTMLEditUtils::IsListItem(&aListItemElement)) {
10665 return NS_ERROR_INVALID_ARG;
10668 if (NS_WARN_IF(!aListItemElement.GetParentElement()) ||
10669 NS_WARN_IF(!aListItemElement.GetParentElement()->GetParentNode())) {
10670 return NS_ERROR_FAILURE;
10673 // if it's first or last list item, don't need to split the list
10674 // otherwise we do.
10675 const bool isFirstListItem = HTMLEditUtils::IsFirstChild(
10676 aListItemElement, {WalkTreeOption::IgnoreNonEditableNode});
10677 const bool isLastListItem = HTMLEditUtils::IsLastChild(
10678 aListItemElement, {WalkTreeOption::IgnoreNonEditableNode});
10680 Element* leftListElement = aListItemElement.GetParentElement();
10681 if (NS_WARN_IF(!leftListElement)) {
10682 return NS_ERROR_FAILURE;
10685 // If it's at middle of parent list element, split the parent list element.
10686 // Then, aListItem becomes the first list item of the right list element.
10687 if (!isFirstListItem && !isLastListItem) {
10688 EditorDOMPoint atListItemElement(&aListItemElement);
10689 if (NS_WARN_IF(!atListItemElement.IsSet())) {
10690 return NS_ERROR_FAILURE;
10692 MOZ_ASSERT(atListItemElement.IsSetAndValid());
10693 Result<SplitNodeResult, nsresult> splitListItemParentResult =
10694 SplitNodeWithTransaction(atListItemElement);
10695 if (MOZ_UNLIKELY(splitListItemParentResult.isErr())) {
10696 NS_WARNING("HTMLEditor::SplitNodeWithTransaction() failed");
10697 return splitListItemParentResult.unwrapErr();
10699 nsresult rv = splitListItemParentResult.inspect().SuggestCaretPointTo(
10700 *this, {SuggestCaret::OnlyIfTransactionsAllowedToDoIt});
10701 if (NS_FAILED(rv)) {
10702 NS_WARNING("SplitNodeResult::SuggestCaretPointTo() failed");
10703 return rv;
10706 leftListElement =
10707 splitListItemParentResult.inspect().GetPreviousContentAs<Element>();
10708 if (MOZ_UNLIKELY(!leftListElement)) {
10709 NS_WARNING(
10710 "HTMLEditor::SplitNodeWithTransaction() didn't return left list "
10711 "element");
10712 return NS_ERROR_FAILURE;
10716 // In most cases, insert the list item into the new left list node..
10717 EditorDOMPoint pointToInsertListItem(leftListElement);
10718 if (NS_WARN_IF(!pointToInsertListItem.IsSet())) {
10719 return NS_ERROR_FAILURE;
10722 // But when the list item was the first child of the right list, it should
10723 // be inserted between the both list elements. This allows user to hit
10724 // Enter twice at a list item breaks the parent list node.
10725 if (!isFirstListItem) {
10726 DebugOnly<bool> advanced = pointToInsertListItem.AdvanceOffset();
10727 NS_WARNING_ASSERTION(advanced,
10728 "Failed to advance offset to right list node");
10731 EditorDOMPoint pointToPutCaret;
10733 Result<MoveNodeResult, nsresult> moveListItemElementResult =
10734 MoveNodeWithTransaction(aListItemElement, pointToInsertListItem);
10735 if (MOZ_UNLIKELY(moveListItemElementResult.isErr())) {
10736 NS_WARNING("HTMLEditor::MoveNodeWithTransaction() failed");
10737 return moveListItemElementResult.unwrapErr();
10739 MoveNodeResult unwrappedMoveListItemElementResult =
10740 moveListItemElementResult.unwrap();
10741 unwrappedMoveListItemElementResult.MoveCaretPointTo(
10742 pointToPutCaret, *this,
10743 {SuggestCaret::OnlyIfHasSuggestion,
10744 SuggestCaret::OnlyIfTransactionsAllowedToDoIt});
10747 // Unwrap list item contents if they are no longer in a list
10748 // XXX If the parent list element is a child of another list element
10749 // (although invalid tree), the list item element won't be unwrapped.
10750 // That makes the parent ancestor element tree valid, but might be
10751 // unexpected result.
10752 // XXX If aListItemElement is <dl> or <dd> and current parent is <ul> or <ol>,
10753 // the list items won't be unwrapped. If aListItemElement is <li> and its
10754 // current parent is <dl>, there is same issue.
10755 if (!HTMLEditUtils::IsAnyListElement(pointToInsertListItem.GetContainer()) &&
10756 HTMLEditUtils::IsListItem(&aListItemElement)) {
10757 Result<EditorDOMPoint, nsresult> unwrapOrphanListItemElementResult =
10758 RemoveBlockContainerWithTransaction(aListItemElement);
10759 if (MOZ_UNLIKELY(unwrapOrphanListItemElementResult.isErr())) {
10760 NS_WARNING("HTMLEditor::RemoveBlockContainerWithTransaction() failed");
10761 return unwrapOrphanListItemElementResult.unwrapErr();
10763 if (AllowsTransactionsToChangeSelection() &&
10764 unwrapOrphanListItemElementResult.inspect().IsSet()) {
10765 pointToPutCaret = unwrapOrphanListItemElementResult.unwrap();
10767 if (!pointToPutCaret.IsSet()) {
10768 return NS_OK;
10770 nsresult rv = CollapseSelectionTo(pointToPutCaret);
10771 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
10772 "EditorBase::CollapseSelectionTo() failed");
10773 return rv;
10776 if (pointToPutCaret.IsSet()) {
10777 nsresult rv = CollapseSelectionTo(pointToPutCaret);
10778 if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) {
10779 NS_WARNING("EditorBase::CollapseSelectionTo() failed");
10780 return rv;
10782 NS_WARNING_ASSERTION(
10783 NS_SUCCEEDED(rv),
10784 "EditorBase::CollapseSelectionTo() failed, but ignored");
10787 if (aLiftUpFromAllParentListElements == LiftUpFromAllParentListElements::No) {
10788 return NS_OK;
10790 // XXX If aListItemElement is moved to unexpected element by mutation event
10791 // listener, shouldn't we stop calling this?
10792 nsresult rv = LiftUpListItemElement(aListItemElement,
10793 LiftUpFromAllParentListElements::Yes);
10794 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
10795 "HTMLEditor::LiftUpListItemElement("
10796 "LiftUpFromAllParentListElements::Yes) failed");
10797 return rv;
10800 nsresult HTMLEditor::DestroyListStructureRecursively(Element& aListElement) {
10801 MOZ_ASSERT(IsEditActionDataAvailable());
10802 MOZ_ASSERT(HTMLEditUtils::IsAnyListElement(&aListElement));
10804 // XXX If mutation event listener inserts new child into `aListElement`,
10805 // this becomes infinite loop so that we should set limit of the
10806 // loop count from original child count.
10807 while (aListElement.GetFirstChild()) {
10808 OwningNonNull<nsIContent> child = *aListElement.GetFirstChild();
10810 if (HTMLEditUtils::IsListItem(child)) {
10811 // XXX Using LiftUpListItemElement() is too expensive for this purpose.
10812 // Looks like the reason why this method uses it is, only this loop
10813 // wants to work with first child of aListElement. However, what it
10814 // actually does is removing <li> as container. Perhaps, we should
10815 // decide destination first, and then, move contents in `child`.
10816 // XXX If aListElement is is a child of another list element (although
10817 // it's invalid tree), this moves the list item to outside of
10818 // aListElement's parent. Is that really intentional behavior?
10819 nsresult rv = LiftUpListItemElement(
10820 MOZ_KnownLive(*child->AsElement()),
10821 HTMLEditor::LiftUpFromAllParentListElements::Yes);
10822 if (NS_FAILED(rv)) {
10823 NS_WARNING(
10824 "HTMLEditor::LiftUpListItemElement(LiftUpFromAllParentListElements:"
10825 ":Yes) failed");
10826 return rv;
10828 continue;
10831 if (HTMLEditUtils::IsAnyListElement(child)) {
10832 nsresult rv =
10833 DestroyListStructureRecursively(MOZ_KnownLive(*child->AsElement()));
10834 if (NS_FAILED(rv)) {
10835 NS_WARNING("HTMLEditor::DestroyListStructureRecursively() failed");
10836 return rv;
10838 continue;
10841 // Delete any non-list items for now
10842 // XXX This is not HTML5 aware. HTML5 allows all list elements to have
10843 // <script> and <template> and <dl> element to have <div> to group
10844 // some <dt> and <dd> elements. So, this may break valid children.
10845 nsresult rv = DeleteNodeWithTransaction(*child);
10846 if (NS_FAILED(rv)) {
10847 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
10848 return rv;
10852 // Delete the now-empty list
10853 const Result<EditorDOMPoint, nsresult> unwrapListElementResult =
10854 RemoveBlockContainerWithTransaction(aListElement);
10855 if (MOZ_UNLIKELY(unwrapListElementResult.isErr())) {
10856 NS_WARNING("HTMLEditor::RemoveBlockContainerWithTransaction() failed");
10857 return unwrapListElementResult.inspectErr();
10859 const EditorDOMPoint& pointToPutCaret = unwrapListElementResult.inspect();
10860 if (!AllowsTransactionsToChangeSelection() || !pointToPutCaret.IsSet()) {
10861 return NS_OK;
10863 nsresult rv = CollapseSelectionTo(pointToPutCaret);
10864 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
10865 "EditorBase::CollapseSelectionTo() failed");
10866 return rv;
10869 nsresult HTMLEditor::EnsureSelectionInBodyOrDocumentElement() {
10870 MOZ_ASSERT(IsEditActionDataAvailable());
10872 RefPtr<Element> bodyOrDocumentElement = GetRoot();
10873 if (NS_WARN_IF(!bodyOrDocumentElement)) {
10874 return NS_ERROR_FAILURE;
10877 const auto atCaret = GetFirstSelectionStartPoint<EditorRawDOMPoint>();
10878 if (NS_WARN_IF(!atCaret.IsSet())) {
10879 return NS_ERROR_FAILURE;
10882 // XXX This does wrong things. Web apps can put any elements as sibling
10883 // of `<body>` element. Therefore, this collapses `Selection` into
10884 // the `<body>` element which `HTMLDocument.body` is set to. So,
10885 // this makes users impossible to modify content outside of the
10886 // `<body>` element even if caret is in an editing host.
10888 // Check that selection start container is inside the <body> element.
10889 // XXXsmaug this code is insane.
10890 nsINode* temp = atCaret.GetContainer();
10891 while (temp && !temp->IsHTMLElement(nsGkAtoms::body)) {
10892 temp = temp->GetParentOrShadowHostNode();
10895 // If we aren't in the <body> element, force the issue.
10896 if (!temp) {
10897 nsresult rv = CollapseSelectionToStartOf(*bodyOrDocumentElement);
10898 if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) {
10899 NS_WARNING(
10900 "EditorBase::CollapseSelectionToStartOf() caused destroying the "
10901 "editor");
10902 return NS_ERROR_EDITOR_DESTROYED;
10904 NS_WARNING_ASSERTION(
10905 NS_SUCCEEDED(rv),
10906 "EditorBase::CollapseSelectionToStartOf() failed, but ignored");
10907 return NS_OK;
10910 const auto selectionEndPoint = GetFirstSelectionEndPoint<EditorRawDOMPoint>();
10911 if (NS_WARN_IF(!selectionEndPoint.IsSet())) {
10912 return NS_ERROR_FAILURE;
10915 // check that selNode is inside body
10916 // XXXsmaug this code is insane.
10917 temp = selectionEndPoint.GetContainer();
10918 while (temp && !temp->IsHTMLElement(nsGkAtoms::body)) {
10919 temp = temp->GetParentOrShadowHostNode();
10922 // If we aren't in the <body> element, force the issue.
10923 if (!temp) {
10924 nsresult rv = CollapseSelectionToStartOf(*bodyOrDocumentElement);
10925 if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) {
10926 NS_WARNING(
10927 "EditorBase::CollapseSelectionToStartOf() caused destroying the "
10928 "editor");
10929 return NS_ERROR_EDITOR_DESTROYED;
10931 NS_WARNING_ASSERTION(
10932 NS_SUCCEEDED(rv),
10933 "EditorBase::CollapseSelectionToStartOf() failed, but ignored");
10936 return NS_OK;
10939 nsresult HTMLEditor::InsertPaddingBRElementForEmptyLastLineIfNeeded(
10940 Element& aElement) {
10941 MOZ_ASSERT(IsEditActionDataAvailable());
10943 if (!HTMLEditUtils::IsBlockElement(
10944 aElement, BlockInlineCheck::UseComputedDisplayStyle)) {
10945 return NS_OK;
10948 if (!HTMLEditUtils::IsEmptyNode(
10949 aElement, {EmptyCheckOption::TreatSingleBRElementAsVisible,
10950 EmptyCheckOption::TreatNonEditableContentAsInvisible})) {
10951 return NS_OK;
10954 Result<CreateElementResult, nsresult> insertPaddingBRElementResult =
10955 InsertPaddingBRElementForEmptyLastLineWithTransaction(
10956 EditorDOMPoint(&aElement, 0u));
10957 if (MOZ_UNLIKELY(insertPaddingBRElementResult.isErr())) {
10958 NS_WARNING(
10959 "HTMLEditor::InsertPaddingBRElementForEmptyLastLineWithTransaction() "
10960 "failed");
10961 return insertPaddingBRElementResult.unwrapErr();
10963 nsresult rv = insertPaddingBRElementResult.inspect().SuggestCaretPointTo(
10964 *this, {SuggestCaret::OnlyIfHasSuggestion,
10965 SuggestCaret::OnlyIfTransactionsAllowedToDoIt,
10966 SuggestCaret::AndIgnoreTrivialError});
10967 if (NS_FAILED(rv)) {
10968 NS_WARNING("CreateElementResult::SuggestCaretPointTo() failed");
10969 return rv;
10971 NS_WARNING_ASSERTION(
10972 rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
10973 "CreateElementResult::SuggestCaretPointTo() failed, but ignored");
10974 return NS_OK;
10977 Result<EditorDOMPoint, nsresult> HTMLEditor::RemoveAlignFromDescendants(
10978 Element& aElement, const nsAString& aAlignType, EditTarget aEditTarget) {
10979 MOZ_ASSERT(IsEditActionDataAvailable());
10980 MOZ_ASSERT(!aElement.IsHTMLElement(nsGkAtoms::table));
10982 const bool useCSS = IsCSSEnabled();
10984 EditorDOMPoint pointToPutCaret;
10986 // Let's remove all alignment hints in the children of aNode; it can
10987 // be an ALIGN attribute (in case we just remove it) or a CENTER
10988 // element (here we have to remove the container and keep its
10989 // children). We break on tables and don't look at their children.
10990 nsCOMPtr<nsIContent> nextSibling;
10991 for (nsIContent* content =
10992 aEditTarget == EditTarget::NodeAndDescendantsExceptTable
10993 ? &aElement
10994 : aElement.GetFirstChild();
10995 content; content = nextSibling) {
10996 // Get the next sibling before removing content from the DOM tree.
10997 // XXX If next sibling is removed from the parent and/or inserted to
10998 // different parent, we will behave unexpectedly. I think that
10999 // we should create child list and handle it with checking whether
11000 // it's still a child of expected parent.
11001 nextSibling = aEditTarget == EditTarget::NodeAndDescendantsExceptTable
11002 ? nullptr
11003 : content->GetNextSibling();
11005 if (content->IsHTMLElement(nsGkAtoms::center)) {
11006 OwningNonNull<Element> centerElement = *content->AsElement();
11008 Result<EditorDOMPoint, nsresult> pointToPutCaretOrError =
11009 RemoveAlignFromDescendants(centerElement, aAlignType,
11010 EditTarget::OnlyDescendantsExceptTable);
11011 if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) {
11012 NS_WARNING(
11013 "HTMLEditor::RemoveAlignFromDescendants(EditTarget::"
11014 "OnlyDescendantsExceptTable) failed");
11015 return pointToPutCaretOrError;
11017 if (pointToPutCaretOrError.inspect().IsSet()) {
11018 pointToPutCaret = pointToPutCaretOrError.unwrap();
11022 // We may have to insert a `<br>` element before first child of the
11023 // `<center>` element because it should be first element of a hard line
11024 // even after removing the `<center>` element.
11026 Result<CreateElementResult, nsresult>
11027 maybeInsertBRElementBeforeFirstChildResult =
11028 EnsureHardLineBeginsWithFirstChildOf(centerElement);
11029 if (MOZ_UNLIKELY(maybeInsertBRElementBeforeFirstChildResult.isErr())) {
11030 NS_WARNING(
11031 "HTMLEditor::EnsureHardLineBeginsWithFirstChildOf() failed");
11032 return maybeInsertBRElementBeforeFirstChildResult.propagateErr();
11034 CreateElementResult unwrappedResult =
11035 maybeInsertBRElementBeforeFirstChildResult.unwrap();
11036 if (unwrappedResult.HasCaretPointSuggestion()) {
11037 pointToPutCaret = unwrappedResult.UnwrapCaretPoint();
11041 // We may have to insert a `<br>` element after last child of the
11042 // `<center>` element because it should be last element of a hard line
11043 // even after removing the `<center>` element.
11045 Result<CreateElementResult, nsresult>
11046 maybeInsertBRElementAfterLastChildResult =
11047 EnsureHardLineEndsWithLastChildOf(centerElement);
11048 if (MOZ_UNLIKELY(maybeInsertBRElementAfterLastChildResult.isErr())) {
11049 NS_WARNING("HTMLEditor::EnsureHardLineEndsWithLastChildOf() failed");
11050 return maybeInsertBRElementAfterLastChildResult.propagateErr();
11052 CreateElementResult unwrappedResult =
11053 maybeInsertBRElementAfterLastChildResult.unwrap();
11054 if (unwrappedResult.HasCaretPointSuggestion()) {
11055 pointToPutCaret = unwrappedResult.UnwrapCaretPoint();
11060 Result<EditorDOMPoint, nsresult> unwrapCenterElementResult =
11061 RemoveContainerWithTransaction(centerElement);
11062 if (MOZ_UNLIKELY(unwrapCenterElementResult.isErr())) {
11063 NS_WARNING("HTMLEditor::RemoveContainerWithTransaction() failed");
11064 return unwrapCenterElementResult;
11066 if (unwrapCenterElementResult.inspect().IsSet()) {
11067 pointToPutCaret = unwrapCenterElementResult.unwrap();
11070 continue;
11073 if (!HTMLEditUtils::IsBlockElement(*content,
11074 BlockInlineCheck::UseHTMLDefaultStyle) &&
11075 !content->IsHTMLElement(nsGkAtoms::hr)) {
11076 continue;
11079 const OwningNonNull<Element> blockOrHRElement = *content->AsElement();
11080 if (HTMLEditUtils::SupportsAlignAttr(blockOrHRElement)) {
11081 nsresult rv =
11082 RemoveAttributeWithTransaction(blockOrHRElement, *nsGkAtoms::align);
11083 if (NS_FAILED(rv)) {
11084 NS_WARNING(
11085 "EditorBase::RemoveAttributeWithTransaction(nsGkAtoms::align) "
11086 "failed");
11087 return Err(rv);
11090 if (useCSS) {
11091 if (blockOrHRElement->IsAnyOfHTMLElements(nsGkAtoms::table,
11092 nsGkAtoms::hr)) {
11093 nsresult rv = SetAttributeOrEquivalent(
11094 blockOrHRElement, nsGkAtoms::align, aAlignType, false);
11095 if (NS_WARN_IF(Destroyed())) {
11096 return Err(NS_ERROR_EDITOR_DESTROYED);
11098 if (NS_FAILED(rv)) {
11099 NS_WARNING(
11100 "EditorBase::SetAttributeOrEquivalent(nsGkAtoms::align) failed");
11101 return Err(rv);
11103 } else {
11104 nsStyledElement* styledBlockOrHRElement =
11105 nsStyledElement::FromNode(blockOrHRElement);
11106 if (NS_WARN_IF(!styledBlockOrHRElement)) {
11107 return Err(NS_ERROR_FAILURE);
11109 // MOZ_KnownLive(*styledBlockOrHRElement): It's `blockOrHRElement
11110 // which is OwningNonNull.
11111 nsAutoString dummyCssValue;
11112 Result<EditorDOMPoint, nsresult> pointToPutCaretOrError =
11113 CSSEditUtils::RemoveCSSInlineStyleWithTransaction(
11114 *this, MOZ_KnownLive(*styledBlockOrHRElement),
11115 nsGkAtoms::textAlign, dummyCssValue);
11116 if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) {
11117 NS_WARNING(
11118 "CSSEditUtils::RemoveCSSInlineStyleWithTransaction(nsGkAtoms::"
11119 "textAlign) failed");
11120 return pointToPutCaretOrError;
11122 if (pointToPutCaretOrError.inspect().IsSet()) {
11123 pointToPutCaret = pointToPutCaretOrError.unwrap();
11127 if (!blockOrHRElement->IsHTMLElement(nsGkAtoms::table)) {
11128 // unless this is a table, look at children
11129 Result<EditorDOMPoint, nsresult> pointToPutCaretOrError =
11130 RemoveAlignFromDescendants(blockOrHRElement, aAlignType,
11131 EditTarget::OnlyDescendantsExceptTable);
11132 if (pointToPutCaretOrError.isErr()) {
11133 NS_WARNING(
11134 "HTMLEditor::RemoveAlignFromDescendants(EditTarget::"
11135 "OnlyDescendantsExceptTable) failed");
11136 return pointToPutCaretOrError;
11138 if (pointToPutCaretOrError.inspect().IsSet()) {
11139 pointToPutCaret = pointToPutCaretOrError.unwrap();
11143 return pointToPutCaret;
11146 Result<CreateElementResult, nsresult>
11147 HTMLEditor::EnsureHardLineBeginsWithFirstChildOf(
11148 Element& aRemovingContainerElement) {
11149 MOZ_ASSERT(IsEditActionDataAvailable());
11151 nsIContent* firstEditableChild = HTMLEditUtils::GetFirstChild(
11152 aRemovingContainerElement, {WalkTreeOption::IgnoreNonEditableNode});
11153 if (!firstEditableChild) {
11154 return CreateElementResult::NotHandled();
11157 if (HTMLEditUtils::IsBlockElement(
11158 *firstEditableChild, BlockInlineCheck::UseComputedDisplayStyle) ||
11159 firstEditableChild->IsHTMLElement(nsGkAtoms::br)) {
11160 return CreateElementResult::NotHandled();
11163 nsIContent* previousEditableContent = HTMLEditUtils::GetPreviousSibling(
11164 aRemovingContainerElement, {WalkTreeOption::IgnoreNonEditableNode});
11165 if (!previousEditableContent) {
11166 return CreateElementResult::NotHandled();
11169 if (HTMLEditUtils::IsBlockElement(
11170 *previousEditableContent,
11171 BlockInlineCheck::UseComputedDisplayStyle) ||
11172 previousEditableContent->IsHTMLElement(nsGkAtoms::br)) {
11173 return CreateElementResult::NotHandled();
11176 Result<CreateElementResult, nsresult> insertBRElementResult = InsertBRElement(
11177 WithTransaction::Yes, EditorDOMPoint(&aRemovingContainerElement, 0u));
11178 NS_WARNING_ASSERTION(
11179 insertBRElementResult.isOk(),
11180 "HTMLEditor::InsertBRElement(WithTransaction::Yes) failed");
11181 return insertBRElementResult;
11184 Result<CreateElementResult, nsresult>
11185 HTMLEditor::EnsureHardLineEndsWithLastChildOf(
11186 Element& aRemovingContainerElement) {
11187 MOZ_ASSERT(IsEditActionDataAvailable());
11189 nsIContent* firstEditableContent = HTMLEditUtils::GetLastChild(
11190 aRemovingContainerElement, {WalkTreeOption::IgnoreNonEditableNode});
11191 if (!firstEditableContent) {
11192 return CreateElementResult::NotHandled();
11195 if (HTMLEditUtils::IsBlockElement(
11196 *firstEditableContent, BlockInlineCheck::UseComputedDisplayStyle) ||
11197 firstEditableContent->IsHTMLElement(nsGkAtoms::br)) {
11198 return CreateElementResult::NotHandled();
11201 nsIContent* nextEditableContent = HTMLEditUtils::GetPreviousSibling(
11202 aRemovingContainerElement, {WalkTreeOption::IgnoreNonEditableNode});
11203 if (!nextEditableContent) {
11204 return CreateElementResult::NotHandled();
11207 if (HTMLEditUtils::IsBlockElement(
11208 *nextEditableContent, BlockInlineCheck::UseComputedDisplayStyle) ||
11209 nextEditableContent->IsHTMLElement(nsGkAtoms::br)) {
11210 return CreateElementResult::NotHandled();
11213 Result<CreateElementResult, nsresult> insertBRElementResult = InsertBRElement(
11214 WithTransaction::Yes, EditorDOMPoint::AtEndOf(aRemovingContainerElement));
11215 NS_WARNING_ASSERTION(
11216 insertBRElementResult.isOk(),
11217 "HTMLEditor::InsertBRElement(WithTransaction::Yes) failed");
11218 return insertBRElementResult;
11221 Result<EditorDOMPoint, nsresult> HTMLEditor::SetBlockElementAlign(
11222 Element& aBlockOrHRElement, const nsAString& aAlignType,
11223 EditTarget aEditTarget) {
11224 MOZ_ASSERT(IsEditActionDataAvailable());
11225 MOZ_ASSERT(HTMLEditUtils::IsBlockElement(
11226 aBlockOrHRElement, BlockInlineCheck::UseHTMLDefaultStyle) ||
11227 aBlockOrHRElement.IsHTMLElement(nsGkAtoms::hr));
11228 MOZ_ASSERT(IsCSSEnabled() ||
11229 HTMLEditUtils::SupportsAlignAttr(aBlockOrHRElement));
11231 EditorDOMPoint pointToPutCaret;
11232 if (!aBlockOrHRElement.IsHTMLElement(nsGkAtoms::table)) {
11233 Result<EditorDOMPoint, nsresult> pointToPutCaretOrError =
11234 RemoveAlignFromDescendants(aBlockOrHRElement, aAlignType, aEditTarget);
11235 if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) {
11236 NS_WARNING("HTMLEditor::RemoveAlignFromDescendants() failed");
11237 return pointToPutCaretOrError;
11239 if (pointToPutCaretOrError.inspect().IsSet()) {
11240 pointToPutCaret = pointToPutCaretOrError.unwrap();
11243 nsresult rv = SetAttributeOrEquivalent(&aBlockOrHRElement, nsGkAtoms::align,
11244 aAlignType, false);
11245 if (NS_WARN_IF(Destroyed())) {
11246 return Err(NS_ERROR_EDITOR_DESTROYED);
11248 if (NS_FAILED(rv)) {
11249 NS_WARNING("HTMLEditor::SetAttributeOrEquivalent(nsGkAtoms::align) failed");
11250 return Err(rv);
11252 return pointToPutCaret;
11255 Result<EditorDOMPoint, nsresult> HTMLEditor::ChangeMarginStart(
11256 Element& aElement, ChangeMargin aChangeMargin,
11257 const Element& aEditingHost) {
11258 MOZ_ASSERT(IsEditActionDataAvailable());
11260 nsStaticAtom& marginProperty = MarginPropertyAtomForIndent(aElement);
11261 if (NS_WARN_IF(Destroyed())) {
11262 return Err(NS_ERROR_EDITOR_DESTROYED);
11264 nsAutoString value;
11265 DebugOnly<nsresult> rvIgnored =
11266 CSSEditUtils::GetSpecifiedProperty(aElement, marginProperty, value);
11267 if (NS_WARN_IF(Destroyed())) {
11268 return Err(NS_ERROR_EDITOR_DESTROYED);
11270 NS_WARNING_ASSERTION(
11271 NS_SUCCEEDED(rvIgnored),
11272 "CSSEditUtils::GetSpecifiedProperty() failed, but ignored");
11273 float f;
11274 RefPtr<nsAtom> unit;
11275 CSSEditUtils::ParseLength(value, &f, getter_AddRefs(unit));
11276 if (!f) {
11277 unit = nsGkAtoms::px;
11279 int8_t multiplier = aChangeMargin == ChangeMargin::Increase ? 1 : -1;
11280 if (nsGkAtoms::in == unit) {
11281 f += NS_EDITOR_INDENT_INCREMENT_IN * multiplier;
11282 } else if (nsGkAtoms::cm == unit) {
11283 f += NS_EDITOR_INDENT_INCREMENT_CM * multiplier;
11284 } else if (nsGkAtoms::mm == unit) {
11285 f += NS_EDITOR_INDENT_INCREMENT_MM * multiplier;
11286 } else if (nsGkAtoms::pt == unit) {
11287 f += NS_EDITOR_INDENT_INCREMENT_PT * multiplier;
11288 } else if (nsGkAtoms::pc == unit) {
11289 f += NS_EDITOR_INDENT_INCREMENT_PC * multiplier;
11290 } else if (nsGkAtoms::em == unit) {
11291 f += NS_EDITOR_INDENT_INCREMENT_EM * multiplier;
11292 } else if (nsGkAtoms::ex == unit) {
11293 f += NS_EDITOR_INDENT_INCREMENT_EX * multiplier;
11294 } else if (nsGkAtoms::px == unit) {
11295 f += NS_EDITOR_INDENT_INCREMENT_PX * multiplier;
11296 } else if (nsGkAtoms::percentage == unit) {
11297 f += NS_EDITOR_INDENT_INCREMENT_PERCENT * multiplier;
11300 if (0 < f) {
11301 if (nsStyledElement* styledElement = nsStyledElement::FromNode(&aElement)) {
11302 nsAutoString newValue;
11303 newValue.AppendFloat(f);
11304 newValue.Append(nsDependentAtomString(unit));
11305 // MOZ_KnownLive(*styledElement): It's aElement and its lifetime must
11306 // be guaranteed by caller because of MOZ_CAN_RUN_SCRIPT method.
11307 // MOZ_KnownLive(merginProperty): It's nsStaticAtom.
11308 nsresult rv = CSSEditUtils::SetCSSPropertyWithTransaction(
11309 *this, MOZ_KnownLive(*styledElement), MOZ_KnownLive(marginProperty),
11310 newValue);
11311 if (rv == NS_ERROR_EDITOR_DESTROYED) {
11312 NS_WARNING(
11313 "CSSEditUtils::SetCSSPropertyWithTransaction() destroyed the "
11314 "editor");
11315 return Err(NS_ERROR_EDITOR_DESTROYED);
11317 NS_WARNING_ASSERTION(
11318 NS_SUCCEEDED(rv),
11319 "CSSEditUtils::SetCSSPropertyWithTransaction() failed, but ignored");
11321 return EditorDOMPoint();
11324 if (nsStyledElement* styledElement = nsStyledElement::FromNode(&aElement)) {
11325 // MOZ_KnownLive(*styledElement): It's aElement and its lifetime must
11326 // be guaranteed by caller because of MOZ_CAN_RUN_SCRIPT method.
11327 // MOZ_KnownLive(merginProperty): It's nsStaticAtom.
11328 nsresult rv = CSSEditUtils::RemoveCSSPropertyWithTransaction(
11329 *this, MOZ_KnownLive(*styledElement), MOZ_KnownLive(marginProperty),
11330 value);
11331 if (rv == NS_ERROR_EDITOR_DESTROYED) {
11332 NS_WARNING(
11333 "CSSEditUtils::RemoveCSSPropertyWithTransaction() destroyed the "
11334 "editor");
11335 return Err(NS_ERROR_EDITOR_DESTROYED);
11337 NS_WARNING_ASSERTION(
11338 NS_SUCCEEDED(rv),
11339 "CSSEditUtils::RemoveCSSPropertyWithTransaction() failed, but ignored");
11342 // Remove unnecessary divs
11343 if (!aElement.IsHTMLElement(nsGkAtoms::div) ||
11344 HTMLEditUtils::ElementHasAttribute(aElement)) {
11345 return EditorDOMPoint();
11347 // Don't touch editing host nor node which is outside of it.
11348 if (&aElement == &aEditingHost ||
11349 !aElement.IsInclusiveDescendantOf(&aEditingHost)) {
11350 return EditorDOMPoint();
11353 Result<EditorDOMPoint, nsresult> unwrapDivElementResult =
11354 RemoveContainerWithTransaction(aElement);
11355 NS_WARNING_ASSERTION(unwrapDivElementResult.isOk(),
11356 "HTMLEditor::RemoveContainerWithTransaction() failed");
11357 return unwrapDivElementResult;
11360 Result<EditActionResult, nsresult>
11361 HTMLEditor::SetSelectionToAbsoluteAsSubAction(const Element& aEditingHost) {
11362 AutoPlaceholderBatch treatAsOneTransaction(
11363 *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
11364 IgnoredErrorResult ignoredError;
11365 AutoEditSubActionNotifier startToHandleEditSubAction(
11366 *this, EditSubAction::eSetPositionToAbsolute, nsIEditor::eNext,
11367 ignoredError);
11368 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
11369 return Err(ignoredError.StealNSResult());
11371 NS_WARNING_ASSERTION(
11372 !ignoredError.Failed(),
11373 "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
11376 Result<EditActionResult, nsresult> result = CanHandleHTMLEditSubAction();
11377 if (MOZ_UNLIKELY(result.isErr())) {
11378 NS_WARNING("HTMLEditor::CanHandleHTMLEditSubAction() failed");
11379 return result;
11381 if (result.inspect().Canceled()) {
11382 return result;
11386 nsresult rv = EnsureNoPaddingBRElementForEmptyEditor();
11387 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
11388 return Err(NS_ERROR_EDITOR_DESTROYED);
11390 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
11391 "EditorBase::EnsureNoPaddingBRElementForEmptyEditor() "
11392 "failed, but ignored");
11394 if (NS_SUCCEEDED(rv) && SelectionRef().IsCollapsed()) {
11395 nsresult rv = EnsureCaretNotAfterInvisibleBRElement();
11396 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
11397 return Err(NS_ERROR_EDITOR_DESTROYED);
11399 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
11400 "HTMLEditor::EnsureCaretNotAfterInvisibleBRElement() "
11401 "failed, but ignored");
11402 if (NS_SUCCEEDED(rv)) {
11403 nsresult rv = PrepareInlineStylesForCaret();
11404 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
11405 return Err(NS_ERROR_EDITOR_DESTROYED);
11407 NS_WARNING_ASSERTION(
11408 NS_SUCCEEDED(rv),
11409 "HTMLEditor::PrepareInlineStylesForCaret() failed, but ignored");
11413 auto EnsureCaretInElementIfCollapsedOutside =
11414 [&](Element& aElement) MOZ_CAN_RUN_SCRIPT {
11415 if (!SelectionRef().IsCollapsed() || !SelectionRef().RangeCount()) {
11416 return NS_OK;
11418 const auto firstRangeStartPoint =
11419 GetFirstSelectionStartPoint<EditorRawDOMPoint>();
11420 if (MOZ_UNLIKELY(!firstRangeStartPoint.IsSet())) {
11421 return NS_OK;
11423 const Result<EditorRawDOMPoint, nsresult> pointToPutCaretOrError =
11424 HTMLEditUtils::ComputePointToPutCaretInElementIfOutside<
11425 EditorRawDOMPoint>(aElement, firstRangeStartPoint);
11426 if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) {
11427 NS_WARNING(
11428 "HTMLEditUtils::ComputePointToPutCaretInElementIfOutside() "
11429 "failed, but ignored");
11430 return NS_OK;
11432 if (!pointToPutCaretOrError.inspect().IsSet()) {
11433 return NS_OK;
11435 nsresult rv = CollapseSelectionTo(pointToPutCaretOrError.inspect());
11436 if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) {
11437 NS_WARNING("EditorBase::CollapseSelectionTo() failed");
11438 return NS_ERROR_EDITOR_DESTROYED;
11440 NS_WARNING_ASSERTION(
11441 NS_SUCCEEDED(rv),
11442 "EditorBase::CollapseSelectionTo() failed, but ignored");
11443 return NS_OK;
11446 RefPtr<Element> focusElement = GetSelectionContainerElement();
11447 if (focusElement && HTMLEditUtils::IsImage(focusElement)) {
11448 nsresult rv = EnsureCaretInElementIfCollapsedOutside(*focusElement);
11449 if (NS_FAILED(rv)) {
11450 NS_WARNING("EnsureCaretInElementIfCollapsedOutside() failed");
11451 return Err(rv);
11453 return EditActionResult::HandledResult();
11456 // XXX Why do we do this only when there is only one selection range?
11457 if (!SelectionRef().IsCollapsed() && SelectionRef().RangeCount() == 1u) {
11458 Result<EditorRawDOMRange, nsresult> extendedRange =
11459 GetRangeExtendedToHardLineEdgesForBlockEditAction(
11460 SelectionRef().GetRangeAt(0u), aEditingHost);
11461 if (MOZ_UNLIKELY(extendedRange.isErr())) {
11462 NS_WARNING(
11463 "HTMLEditor::GetRangeExtendedToHardLineEdgesForBlockEditAction() "
11464 "failed");
11465 return extendedRange.propagateErr();
11467 // Note that end point may be prior to start point. So, we
11468 // cannot use Selection::SetStartAndEndInLimit() here.
11469 IgnoredErrorResult error;
11470 SelectionRef().SetBaseAndExtentInLimiter(
11471 extendedRange.inspect().StartRef().ToRawRangeBoundary(),
11472 extendedRange.inspect().EndRef().ToRawRangeBoundary(), error);
11473 if (NS_WARN_IF(Destroyed())) {
11474 return Err(NS_ERROR_EDITOR_DESTROYED);
11476 if (MOZ_UNLIKELY(error.Failed())) {
11477 NS_WARNING("Selection::SetBaseAndExtentInLimiter() failed");
11478 return Err(error.StealNSResult());
11482 RefPtr<Element> divElement;
11483 rv = MoveSelectedContentsToDivElementToMakeItAbsolutePosition(
11484 address_of(divElement), aEditingHost);
11485 // MoveSelectedContentsToDivElementToMakeItAbsolutePosition() may restore
11486 // selection with AutoSelectionRestorer. Therefore, the editor might have
11487 // already been destroyed now.
11488 if (NS_WARN_IF(Destroyed())) {
11489 return Err(NS_ERROR_EDITOR_DESTROYED);
11491 if (NS_FAILED(rv)) {
11492 NS_WARNING(
11493 "HTMLEditor::MoveSelectedContentsToDivElementToMakeItAbsolutePosition()"
11494 " failed");
11495 return Err(rv);
11498 if (IsSelectionRangeContainerNotContent()) {
11499 NS_WARNING("Mutation event listener might have changed the selection");
11500 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
11503 rv = MaybeInsertPaddingBRElementForEmptyLastLineAtSelection();
11504 if (NS_FAILED(rv)) {
11505 NS_WARNING(
11506 "HTMLEditor::MaybeInsertPaddingBRElementForEmptyLastLineAtSelection() "
11507 "failed");
11508 return Err(rv);
11511 if (!divElement) {
11512 return EditActionResult::HandledResult();
11515 rv = SetPositionToAbsoluteOrStatic(*divElement, true);
11516 if (NS_WARN_IF(Destroyed())) {
11517 return Err(NS_ERROR_EDITOR_DESTROYED);
11519 if (NS_FAILED(rv)) {
11520 NS_WARNING("HTMLEditor::SetPositionToAbsoluteOrStatic() failed");
11521 return Err(rv);
11524 rv = EnsureCaretInElementIfCollapsedOutside(*divElement);
11525 if (NS_FAILED(rv)) {
11526 NS_WARNING("EnsureCaretInElementIfCollapsedOutside() failed");
11527 return Err(rv);
11529 return EditActionResult::HandledResult();
11532 nsresult HTMLEditor::MoveSelectedContentsToDivElementToMakeItAbsolutePosition(
11533 RefPtr<Element>* aTargetElement, const Element& aEditingHost) {
11534 MOZ_ASSERT(IsEditActionDataAvailable());
11535 MOZ_ASSERT(aTargetElement);
11537 AutoSelectionRestorer restoreSelectionLater(this);
11539 EditorDOMPoint pointToPutCaret;
11541 // Use these ranges to construct a list of nodes to act on.
11542 AutoTArray<OwningNonNull<nsIContent>, 64> arrayOfContents;
11544 AutoRangeArray extendedSelectionRanges(SelectionRef());
11545 extendedSelectionRanges.ExtendRangesToWrapLines(
11546 EditSubAction::eSetPositionToAbsolute,
11547 BlockInlineCheck::UseHTMLDefaultStyle, aEditingHost);
11548 Result<EditorDOMPoint, nsresult> splitResult =
11549 extendedSelectionRanges
11550 .SplitTextAtEndBoundariesAndInlineAncestorsAtBothBoundaries(
11551 *this, BlockInlineCheck::UseHTMLDefaultStyle, aEditingHost);
11552 if (MOZ_UNLIKELY(splitResult.isErr())) {
11553 NS_WARNING(
11554 "AutoRangeArray::"
11555 "SplitTextAtEndBoundariesAndInlineAncestorsAtBothBoundaries() "
11556 "failed");
11557 return splitResult.unwrapErr();
11559 if (splitResult.inspect().IsSet()) {
11560 pointToPutCaret = splitResult.unwrap();
11562 nsresult rv = extendedSelectionRanges.CollectEditTargetNodes(
11563 *this, arrayOfContents, EditSubAction::eSetPositionToAbsolute,
11564 AutoRangeArray::CollectNonEditableNodes::Yes);
11565 if (NS_FAILED(rv)) {
11566 NS_WARNING(
11567 "AutoRangeArray::CollectEditTargetNodes(EditSubAction::"
11568 "eSetPositionToAbsolute, CollectNonEditableNodes::Yes) failed");
11569 return rv;
11573 Result<EditorDOMPoint, nsresult> splitAtBRElementsResult =
11574 MaybeSplitElementsAtEveryBRElement(arrayOfContents,
11575 EditSubAction::eSetPositionToAbsolute);
11576 if (MOZ_UNLIKELY(splitAtBRElementsResult.isErr())) {
11577 NS_WARNING(
11578 "HTMLEditor::MaybeSplitElementsAtEveryBRElement(EditSubAction::"
11579 "eSetPositionToAbsolute) failed");
11580 return splitAtBRElementsResult.inspectErr();
11582 if (splitAtBRElementsResult.inspect().IsSet()) {
11583 pointToPutCaret = splitAtBRElementsResult.unwrap();
11586 if (AllowsTransactionsToChangeSelection() &&
11587 pointToPutCaret.IsSetAndValid()) {
11588 nsresult rv = CollapseSelectionTo(pointToPutCaret);
11589 if (NS_FAILED(rv)) {
11590 NS_WARNING("EditorBase::CollapseSelectionTo() failed");
11591 return rv;
11595 // If there is no visible and editable nodes in the edit targets, make an
11596 // empty block.
11597 // XXX Isn't this odd if there are only non-editable visible nodes?
11598 if (HTMLEditUtils::IsEmptyOneHardLine(
11599 arrayOfContents, BlockInlineCheck::UseHTMLDefaultStyle)) {
11600 const auto atCaret =
11601 EditorBase::GetFirstSelectionStartPoint<EditorDOMPoint>();
11602 if (NS_WARN_IF(!atCaret.IsSet())) {
11603 return NS_ERROR_FAILURE;
11606 // Make sure we can put a block here.
11607 Result<CreateElementResult, nsresult> createNewDivElementResult =
11608 InsertElementWithSplittingAncestorsWithTransaction(
11609 *nsGkAtoms::div, atCaret, BRElementNextToSplitPoint::Keep,
11610 aEditingHost);
11611 if (MOZ_UNLIKELY(createNewDivElementResult.isErr())) {
11612 NS_WARNING(
11613 "HTMLEditor::InsertElementWithSplittingAncestorsWithTransaction("
11614 "nsGkAtoms::div) failed");
11615 return createNewDivElementResult.unwrapErr();
11617 CreateElementResult unwrappedCreateNewDivElementResult =
11618 createNewDivElementResult.unwrap();
11619 // We'll update selection after deleting the content nodes and nobody
11620 // refers selection until then. Therefore, we don't need to update
11621 // selection here.
11622 unwrappedCreateNewDivElementResult.IgnoreCaretPointSuggestion();
11623 RefPtr<Element> newDivElement =
11624 unwrappedCreateNewDivElementResult.UnwrapNewNode();
11625 MOZ_ASSERT(newDivElement);
11626 // Delete anything that was in the list of nodes
11627 // XXX We don't need to remove items from the array.
11628 for (OwningNonNull<nsIContent>& curNode : arrayOfContents) {
11629 // MOZ_KnownLive because 'arrayOfContents' is guaranteed to keep it alive.
11630 nsresult rv = DeleteNodeWithTransaction(MOZ_KnownLive(*curNode));
11631 if (NS_FAILED(rv)) {
11632 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
11633 return rv;
11636 // Don't restore the selection
11637 restoreSelectionLater.Abort();
11638 nsresult rv = CollapseSelectionToStartOf(*newDivElement);
11639 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
11640 "EditorBase::CollapseSelectionToStartOf() failed");
11641 *aTargetElement = std::move(newDivElement);
11642 return rv;
11645 // `<div>` element to be positioned absolutely. This may have already
11646 // existed or newly created by this method.
11647 RefPtr<Element> targetDivElement;
11648 // Newly created list element for moving selected list item elements into
11649 // targetDivElement. I.e., this is created in the `<div>` element.
11650 RefPtr<Element> createdListElement;
11651 // If we handle a parent list item element, this is set to it. In such case,
11652 // we should handle its children again.
11653 RefPtr<Element> handledListItemElement;
11654 for (OwningNonNull<nsIContent>& content : arrayOfContents) {
11655 // Here's where we actually figure out what to do.
11656 EditorDOMPoint atContent(content);
11657 if (NS_WARN_IF(!atContent.IsSet())) {
11658 return NS_ERROR_FAILURE; // XXX not continue??
11661 // Ignore all non-editable nodes. Leave them be.
11662 if (!EditorUtils::IsEditableContent(content, EditorType::HTML)) {
11663 continue;
11666 // If current node is a child of a list element, we need another list
11667 // element in absolute-positioned `<div>` element to avoid non-selected
11668 // list items are moved into the `<div>` element.
11669 if (HTMLEditUtils::IsAnyListElement(atContent.GetContainer())) {
11670 // If we cannot move current node to created list element, we need a
11671 // list element in the target `<div>` element for the destination.
11672 // Therefore, duplicate same list element into the target `<div>`
11673 // element.
11674 nsIContent* previousEditableContent =
11675 createdListElement
11676 ? HTMLEditUtils::GetPreviousSibling(
11677 content, {WalkTreeOption::IgnoreNonEditableNode})
11678 : nullptr;
11679 if (!createdListElement ||
11680 (previousEditableContent &&
11681 previousEditableContent != createdListElement)) {
11682 nsAtom* ULOrOLOrDLTagName =
11683 atContent.GetContainer()->NodeInfo()->NameAtom();
11684 if (targetDivElement) {
11685 // XXX Do we need to split the container? Since we'll append new
11686 // element at end of the <div> element.
11687 Result<SplitNodeResult, nsresult> splitNodeResult =
11688 MaybeSplitAncestorsForInsertWithTransaction(
11689 MOZ_KnownLive(*ULOrOLOrDLTagName), atContent, aEditingHost);
11690 if (MOZ_UNLIKELY(splitNodeResult.isErr())) {
11691 NS_WARNING(
11692 "HTMLEditor::MaybeSplitAncestorsForInsertWithTransaction() "
11693 "failed");
11694 return splitNodeResult.unwrapErr();
11696 // We'll update selection after creating a list element below.
11697 // Therefore, we don't need to touch selection here.
11698 splitNodeResult.inspect().IgnoreCaretPointSuggestion();
11699 } else {
11700 // If we've not had a target <div> element yet, let's insert a <div>
11701 // element with splitting the ancestors.
11702 Result<CreateElementResult, nsresult> createNewDivElementResult =
11703 InsertElementWithSplittingAncestorsWithTransaction(
11704 *nsGkAtoms::div, atContent, BRElementNextToSplitPoint::Keep,
11705 aEditingHost);
11706 if (MOZ_UNLIKELY(createNewDivElementResult.isErr())) {
11707 NS_WARNING(
11708 "HTMLEditor::"
11709 "InsertElementWithSplittingAncestorsWithTransaction(nsGkAtoms::"
11710 "div) failed");
11711 return createNewDivElementResult.unwrapErr();
11713 // We'll update selection after creating a list element below.
11714 // Therefor, we don't need to touch selection here.
11715 createNewDivElementResult.inspect().IgnoreCaretPointSuggestion();
11716 MOZ_ASSERT(createNewDivElementResult.inspect().GetNewNode());
11717 targetDivElement = createNewDivElementResult.unwrap().UnwrapNewNode();
11719 Result<CreateElementResult, nsresult> createNewListElementResult =
11720 CreateAndInsertElement(WithTransaction::Yes,
11721 MOZ_KnownLive(*ULOrOLOrDLTagName),
11722 EditorDOMPoint::AtEndOf(targetDivElement));
11723 if (MOZ_UNLIKELY(createNewListElementResult.isErr())) {
11724 NS_WARNING(
11725 "HTMLEditor::CreateAndInsertElement(WithTransaction::Yes) "
11726 "failed");
11727 return createNewListElementResult.unwrapErr();
11729 nsresult rv = createNewListElementResult.inspect().SuggestCaretPointTo(
11730 *this, {SuggestCaret::OnlyIfHasSuggestion,
11731 SuggestCaret::OnlyIfTransactionsAllowedToDoIt,
11732 SuggestCaret::AndIgnoreTrivialError});
11733 if (NS_FAILED(rv)) {
11734 NS_WARNING("CreateElementResult::SuggestCaretPointTo() failed");
11735 return Err(rv);
11737 NS_WARNING_ASSERTION(
11738 rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
11739 "CreateElementResult::SuggestCaretPointTo() failed, but ignored");
11740 createdListElement =
11741 createNewListElementResult.unwrap().UnwrapNewNode();
11742 MOZ_ASSERT(createdListElement);
11744 // Move current node (maybe, assumed as a list item element) into the
11745 // new list element in the target `<div>` element to be positioned
11746 // absolutely.
11747 // MOZ_KnownLive because 'arrayOfContents' is guaranteed to keep it alive.
11748 Result<MoveNodeResult, nsresult> moveNodeResult =
11749 MoveNodeToEndWithTransaction(MOZ_KnownLive(content),
11750 *createdListElement);
11751 if (MOZ_UNLIKELY(moveNodeResult.isErr())) {
11752 NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed");
11753 return moveNodeResult.propagateErr();
11755 nsresult rv = moveNodeResult.inspect().SuggestCaretPointTo(
11756 *this, {SuggestCaret::OnlyIfHasSuggestion,
11757 SuggestCaret::OnlyIfTransactionsAllowedToDoIt,
11758 SuggestCaret::AndIgnoreTrivialError});
11759 if (NS_FAILED(rv)) {
11760 NS_WARNING("MoveNodeResult::SuggestCaretPointTo() failed");
11761 return Err(rv);
11763 NS_WARNING_ASSERTION(
11764 rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
11765 "MoveNodeResult::SuggestCaretPointTo() failed, but ignored");
11766 continue;
11769 // If contents in a list item element is selected, we should move current
11770 // node into the target `<div>` element with the list item element itself
11771 // because we want to keep indent level of the contents.
11772 if (RefPtr<Element> listItemElement =
11773 HTMLEditUtils::GetClosestAncestorListItemElement(content,
11774 &aEditingHost)) {
11775 if (handledListItemElement == listItemElement) {
11776 // Current node has already been moved into the `<div>` element.
11777 continue;
11779 // If we cannot move the list item element into created list element,
11780 // we need another list element in the target `<div>` element.
11781 nsIContent* previousEditableContent =
11782 createdListElement
11783 ? HTMLEditUtils::GetPreviousSibling(
11784 *listItemElement, {WalkTreeOption::IgnoreNonEditableNode})
11785 : nullptr;
11786 if (!createdListElement ||
11787 (previousEditableContent &&
11788 previousEditableContent != createdListElement)) {
11789 EditorDOMPoint atListItem(listItemElement);
11790 if (NS_WARN_IF(!atListItem.IsSet())) {
11791 return NS_ERROR_FAILURE;
11793 // XXX If content is the listItemElement and not in a list element,
11794 // we duplicate wrong element into the target `<div>` element.
11795 nsAtom* containerName =
11796 atListItem.GetContainer()->NodeInfo()->NameAtom();
11797 if (targetDivElement) {
11798 // XXX Do we need to split the container? Since we'll append new
11799 // element at end of the <div> element.
11800 Result<SplitNodeResult, nsresult> splitNodeResult =
11801 MaybeSplitAncestorsForInsertWithTransaction(
11802 MOZ_KnownLive(*containerName), atListItem, aEditingHost);
11803 if (MOZ_UNLIKELY(splitNodeResult.isErr())) {
11804 NS_WARNING(
11805 "HTMLEditor::MaybeSplitAncestorsForInsertWithTransaction() "
11806 "failed");
11807 return splitNodeResult.unwrapErr();
11809 // We'll update selection after creating a list element below.
11810 // Therefore, we don't need to touch selection here.
11811 splitNodeResult.inspect().IgnoreCaretPointSuggestion();
11812 } else {
11813 // If we've not had a target <div> element yet, let's insert a <div>
11814 // element with splitting the ancestors.
11815 Result<CreateElementResult, nsresult> createNewDivElementResult =
11816 InsertElementWithSplittingAncestorsWithTransaction(
11817 *nsGkAtoms::div, atContent, BRElementNextToSplitPoint::Keep,
11818 aEditingHost);
11819 if (MOZ_UNLIKELY(createNewDivElementResult.isErr())) {
11820 NS_WARNING(
11821 "HTMLEditor::"
11822 "InsertElementWithSplittingAncestorsWithTransaction("
11823 "nsGkAtoms::div) failed");
11824 return createNewDivElementResult.unwrapErr();
11826 // We'll update selection after creating a list element below.
11827 // Therefore, we don't need to touch selection here.
11828 createNewDivElementResult.inspect().IgnoreCaretPointSuggestion();
11829 MOZ_ASSERT(createNewDivElementResult.inspect().GetNewNode());
11830 targetDivElement = createNewDivElementResult.unwrap().UnwrapNewNode();
11832 // XXX So, createdListElement may be set to a non-list element.
11833 Result<CreateElementResult, nsresult> createNewListElementResult =
11834 CreateAndInsertElement(WithTransaction::Yes,
11835 MOZ_KnownLive(*containerName),
11836 EditorDOMPoint::AtEndOf(targetDivElement));
11837 if (MOZ_UNLIKELY(createNewListElementResult.isErr())) {
11838 NS_WARNING(
11839 "HTMLEditor::CreateAndInsertElement(WithTransaction::Yes) "
11840 "failed");
11841 return createNewListElementResult.unwrapErr();
11843 nsresult rv = createNewListElementResult.inspect().SuggestCaretPointTo(
11844 *this, {SuggestCaret::OnlyIfHasSuggestion,
11845 SuggestCaret::OnlyIfTransactionsAllowedToDoIt,
11846 SuggestCaret::AndIgnoreTrivialError});
11847 if (NS_FAILED(rv)) {
11848 NS_WARNING("CreateElementResult::SuggestCaretPointTo() failed");
11849 return Err(rv);
11851 NS_WARNING_ASSERTION(
11852 rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
11853 "CreateElementResult::SuggestCaretPointTo() failed, but ignored");
11854 createdListElement =
11855 createNewListElementResult.unwrap().UnwrapNewNode();
11856 MOZ_ASSERT(createdListElement);
11858 // Move current list item element into the createdListElement (could be
11859 // non-list element due to the above bug) in a candidate `<div>` element
11860 // to be positioned absolutely.
11861 Result<MoveNodeResult, nsresult> moveListItemElementResult =
11862 MoveNodeToEndWithTransaction(*listItemElement, *createdListElement);
11863 if (MOZ_UNLIKELY(moveListItemElementResult.isErr())) {
11864 NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed");
11865 return moveListItemElementResult.unwrapErr();
11867 nsresult rv = moveListItemElementResult.inspect().SuggestCaretPointTo(
11868 *this, {SuggestCaret::OnlyIfHasSuggestion,
11869 SuggestCaret::OnlyIfTransactionsAllowedToDoIt,
11870 SuggestCaret::AndIgnoreTrivialError});
11871 if (NS_FAILED(rv)) {
11872 NS_WARNING("MoveNodeResult::SuggestCaretPointTo() failed");
11873 return Err(rv);
11875 NS_WARNING_ASSERTION(
11876 rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
11877 "MoveNodeResult::SuggestCaretPointTo() failed, but ignored");
11878 handledListItemElement = std::move(listItemElement);
11879 continue;
11882 if (!targetDivElement) {
11883 // If we meet a `<div>` element, use it as the absolute-position
11884 // container.
11885 // XXX This looks odd. If there are 2 or more `<div>` elements are
11886 // selected, first found `<div>` element will have all other
11887 // selected nodes.
11888 if (content->IsHTMLElement(nsGkAtoms::div)) {
11889 targetDivElement = content->AsElement();
11890 MOZ_ASSERT(!createdListElement);
11891 MOZ_ASSERT(!handledListItemElement);
11892 continue;
11894 // Otherwise, create new `<div>` element to be positioned absolutely
11895 // and to contain all selected nodes.
11896 Result<CreateElementResult, nsresult> createNewDivElementResult =
11897 InsertElementWithSplittingAncestorsWithTransaction(
11898 *nsGkAtoms::div, atContent, BRElementNextToSplitPoint::Keep,
11899 aEditingHost);
11900 if (MOZ_UNLIKELY(createNewDivElementResult.isErr())) {
11901 NS_WARNING(
11902 "HTMLEditor::InsertElementWithSplittingAncestorsWithTransaction("
11903 "nsGkAtoms::div) failed");
11904 return createNewDivElementResult.unwrapErr();
11906 nsresult rv = createNewDivElementResult.inspect().SuggestCaretPointTo(
11907 *this, {SuggestCaret::OnlyIfHasSuggestion,
11908 SuggestCaret::OnlyIfTransactionsAllowedToDoIt});
11909 if (NS_FAILED(rv)) {
11910 NS_WARNING("CreateElementResult::SuggestCaretPointTo() failed");
11911 return rv;
11913 MOZ_ASSERT(createNewDivElementResult.inspect().GetNewNode());
11914 targetDivElement = createNewDivElementResult.unwrap().UnwrapNewNode();
11917 // MOZ_KnownLive because 'arrayOfContents' is guaranteed to keep it alive.
11918 Result<MoveNodeResult, nsresult> moveNodeResult =
11919 MoveNodeToEndWithTransaction(MOZ_KnownLive(content), *targetDivElement);
11920 if (MOZ_UNLIKELY(moveNodeResult.isErr())) {
11921 NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed");
11922 return moveNodeResult.unwrapErr();
11924 nsresult rv = moveNodeResult.inspect().SuggestCaretPointTo(
11925 *this, {SuggestCaret::OnlyIfHasSuggestion,
11926 SuggestCaret::OnlyIfTransactionsAllowedToDoIt,
11927 SuggestCaret::AndIgnoreTrivialError});
11928 if (NS_FAILED(rv)) {
11929 NS_WARNING("MoveNodeResult::SuggestCaretPointTo() failed");
11930 return rv;
11932 NS_WARNING_ASSERTION(
11933 rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
11934 "MoveNodeResult::SuggestCaretPointTo() failed, but ignored");
11935 // Forget createdListElement, if any
11936 createdListElement = nullptr;
11938 *aTargetElement = std::move(targetDivElement);
11939 return NS_OK;
11942 Result<EditActionResult, nsresult>
11943 HTMLEditor::SetSelectionToStaticAsSubAction() {
11944 MOZ_ASSERT(IsEditActionDataAvailable());
11946 AutoPlaceholderBatch treatAsOneTransaction(
11947 *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
11948 IgnoredErrorResult ignoredError;
11949 AutoEditSubActionNotifier startToHandleEditSubAction(
11950 *this, EditSubAction::eSetPositionToStatic, nsIEditor::eNext,
11951 ignoredError);
11952 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
11953 return Err(ignoredError.StealNSResult());
11955 NS_WARNING_ASSERTION(
11956 !ignoredError.Failed(),
11957 "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
11960 Result<EditActionResult, nsresult> result = CanHandleHTMLEditSubAction();
11961 if (MOZ_UNLIKELY(result.isErr())) {
11962 NS_WARNING("HTMLEditor::CanHandleHTMLEditSubAction() failed");
11963 return result;
11965 if (result.inspect().Canceled()) {
11966 return result;
11970 nsresult rv = EnsureNoPaddingBRElementForEmptyEditor();
11971 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
11972 return Err(NS_ERROR_EDITOR_DESTROYED);
11974 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
11975 "EditorBase::EnsureNoPaddingBRElementForEmptyEditor() "
11976 "failed, but ignored");
11978 if (NS_SUCCEEDED(rv) && SelectionRef().IsCollapsed()) {
11979 nsresult rv = EnsureCaretNotAfterInvisibleBRElement();
11980 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
11981 return Err(NS_ERROR_EDITOR_DESTROYED);
11983 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
11984 "HTMLEditor::EnsureCaretNotAfterInvisibleBRElement() "
11985 "failed, but ignored");
11986 if (NS_SUCCEEDED(rv)) {
11987 nsresult rv = PrepareInlineStylesForCaret();
11988 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
11989 return Err(NS_ERROR_EDITOR_DESTROYED);
11991 NS_WARNING_ASSERTION(
11992 NS_SUCCEEDED(rv),
11993 "HTMLEditor::PrepareInlineStylesForCaret() failed, but ignored");
11997 RefPtr<Element> element = GetAbsolutelyPositionedSelectionContainer();
11998 if (!element) {
11999 if (NS_WARN_IF(Destroyed())) {
12000 return Err(NS_ERROR_EDITOR_DESTROYED);
12002 NS_WARNING(
12003 "HTMLEditor::GetAbsolutelyPositionedSelectionContainer() returned "
12004 "nullptr");
12005 return Err(NS_ERROR_FAILURE);
12009 AutoSelectionRestorer restoreSelectionLater(this);
12011 nsresult rv = SetPositionToAbsoluteOrStatic(*element, false);
12012 if (NS_WARN_IF(Destroyed())) {
12013 return Err(NS_ERROR_EDITOR_DESTROYED);
12015 if (NS_FAILED(rv)) {
12016 NS_WARNING("HTMLEditor::SetPositionToAbsoluteOrStatic() failed");
12017 return Err(rv);
12021 // Restoring Selection might cause destroying the HTML editor.
12022 if (MOZ_UNLIKELY(Destroyed())) {
12023 NS_WARNING("Destroying AutoSelectionRestorer caused destroying the editor");
12024 return Err(NS_ERROR_EDITOR_DESTROYED);
12026 return EditActionResult::HandledResult();
12029 Result<EditActionResult, nsresult> HTMLEditor::AddZIndexAsSubAction(
12030 int32_t aChange) {
12031 MOZ_ASSERT(IsEditActionDataAvailable());
12033 AutoPlaceholderBatch treatAsOneTransaction(
12034 *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
12035 IgnoredErrorResult ignoredError;
12036 AutoEditSubActionNotifier startToHandleEditSubAction(
12037 *this,
12038 aChange < 0 ? EditSubAction::eDecreaseZIndex
12039 : EditSubAction::eIncreaseZIndex,
12040 nsIEditor::eNext, ignoredError);
12041 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
12042 return Err(ignoredError.StealNSResult());
12044 NS_WARNING_ASSERTION(
12045 !ignoredError.Failed(),
12046 "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
12049 Result<EditActionResult, nsresult> result = CanHandleHTMLEditSubAction();
12050 if (MOZ_UNLIKELY(result.isErr())) {
12051 NS_WARNING("HTMLEditor::CanHandleHTMLEditSubAction() failed");
12052 return result;
12054 if (result.inspect().Canceled()) {
12055 return result;
12059 nsresult rv = EnsureNoPaddingBRElementForEmptyEditor();
12060 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
12061 return Err(NS_ERROR_EDITOR_DESTROYED);
12063 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
12064 "EditorBase::EnsureNoPaddingBRElementForEmptyEditor() "
12065 "failed, but ignored");
12067 if (NS_SUCCEEDED(rv) && SelectionRef().IsCollapsed()) {
12068 nsresult rv = EnsureCaretNotAfterInvisibleBRElement();
12069 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
12070 return Err(NS_ERROR_EDITOR_DESTROYED);
12072 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
12073 "HTMLEditor::EnsureCaretNotAfterInvisibleBRElement() "
12074 "failed, but ignored");
12075 if (NS_SUCCEEDED(rv)) {
12076 nsresult rv = PrepareInlineStylesForCaret();
12077 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
12078 return Err(NS_ERROR_EDITOR_DESTROYED);
12080 NS_WARNING_ASSERTION(
12081 NS_SUCCEEDED(rv),
12082 "HTMLEditor::PrepareInlineStylesForCaret() failed, but ignored");
12086 RefPtr<Element> absolutelyPositionedElement =
12087 GetAbsolutelyPositionedSelectionContainer();
12088 if (!absolutelyPositionedElement) {
12089 if (NS_WARN_IF(Destroyed())) {
12090 return Err(NS_ERROR_EDITOR_DESTROYED);
12092 NS_WARNING(
12093 "HTMLEditor::GetAbsolutelyPositionedSelectionContainer() returned "
12094 "nullptr");
12095 return Err(NS_ERROR_FAILURE);
12098 nsStyledElement* absolutelyPositionedStyledElement =
12099 nsStyledElement::FromNode(absolutelyPositionedElement);
12100 if (NS_WARN_IF(!absolutelyPositionedStyledElement)) {
12101 return Err(NS_ERROR_FAILURE);
12105 AutoSelectionRestorer restoreSelectionLater(this);
12107 // MOZ_KnownLive(*absolutelyPositionedStyledElement): It's
12108 // absolutelyPositionedElement whose type is RefPtr.
12109 Result<int32_t, nsresult> result = AddZIndexWithTransaction(
12110 MOZ_KnownLive(*absolutelyPositionedStyledElement), aChange);
12111 if (MOZ_UNLIKELY(result.isErr())) {
12112 NS_WARNING("HTMLEditor::AddZIndexWithTransaction() failed");
12113 return result.propagateErr();
12117 // Restoring Selection might cause destroying the HTML editor.
12118 if (MOZ_UNLIKELY(Destroyed())) {
12119 NS_WARNING("Destroying AutoSelectionRestorer caused destroying the editor");
12120 return Err(NS_ERROR_EDITOR_DESTROYED);
12123 return EditActionResult::HandledResult();
12126 nsresult HTMLEditor::OnDocumentModified() {
12127 if (mPendingDocumentModifiedRunner) {
12128 return NS_OK; // We've already posted same runnable into the queue.
12130 mPendingDocumentModifiedRunner = NewRunnableMethod(
12131 "HTMLEditor::OnModifyDocument", this, &HTMLEditor::OnModifyDocument);
12132 nsContentUtils::AddScriptRunner(do_AddRef(mPendingDocumentModifiedRunner));
12133 // Be aware, if OnModifyDocument() may be called synchronously, the
12134 // editor might have been destroyed here.
12135 return NS_WARN_IF(Destroyed()) ? NS_ERROR_EDITOR_DESTROYED : NS_OK;
12138 } // namespace mozilla