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