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