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"
12 #include "CSSEditUtils.h"
13 #include "EditAction.h"
14 #include "EditorDOMPoint.h"
15 #include "EditorUtils.h"
16 #include "HTMLEditHelpers.h"
17 #include "HTMLEditUtils.h"
18 #include "TypeInState.h" // for SpecifiedStyle
19 #include "WSRunObject.h"
21 #include "mozilla/Assertions.h"
22 #include "mozilla/CheckedInt.h"
23 #include "mozilla/ContentIterator.h"
24 #include "mozilla/IntegerRange.h"
25 #include "mozilla/InternalMutationEvent.h"
26 #include "mozilla/MathAlgorithms.h"
27 #include "mozilla/Maybe.h"
28 #include "mozilla/OwningNonNull.h"
29 #include "mozilla/Preferences.h"
30 #include "mozilla/RangeUtils.h"
31 #include "mozilla/StaticPrefs_editor.h" // for StaticPrefs::editor_*
32 #include "mozilla/TextComposition.h"
33 #include "mozilla/UniquePtr.h"
34 #include "mozilla/Unused.h"
35 #include "mozilla/dom/AncestorIterator.h"
36 #include "mozilla/dom/Element.h"
37 #include "mozilla/dom/HTMLBRElement.h"
38 #include "mozilla/dom/RangeBinding.h"
39 #include "mozilla/dom/Selection.h"
40 #include "mozilla/dom/StaticRange.h"
41 #include "mozilla/mozalloc.h"
42 #include "nsAString.h"
43 #include "nsAlgorithm.h"
46 #include "nsCRTGlue.h"
47 #include "nsComponentManagerUtils.h"
48 #include "nsContentUtils.h"
51 #include "nsFrameSelection.h"
52 #include "nsGkAtoms.h"
53 #include "nsHTMLDocument.h"
54 #include "nsIContent.h"
58 #include "nsLiteralString.h"
59 #include "nsPrintfCString.h"
61 #include "nsReadableUtils.h"
63 #include "nsStringFwd.h"
64 #include "nsStyledElement.h"
66 #include "nsTextNode.h"
67 #include "nsThreadUtils.h"
68 #include "nsUnicharUtils.h"
70 // Workaround for windows headers
80 using EmptyCheckOption
= HTMLEditUtils::EmptyCheckOption
;
81 using LeafNodeType
= HTMLEditUtils::LeafNodeType
;
82 using LeafNodeTypes
= HTMLEditUtils::LeafNodeTypes
;
83 using StyleDifference
= HTMLEditUtils::StyleDifference
;
84 using WalkTextOption
= HTMLEditUtils::WalkTextOption
;
85 using WalkTreeDirection
= HTMLEditUtils::WalkTreeDirection
;
86 using WalkTreeOption
= HTMLEditUtils::WalkTreeOption
;
88 /********************************************************
89 * first some helpful functors we will use
90 ********************************************************/
92 static bool IsStyleCachePreservingSubAction(EditSubAction aEditSubAction
) {
93 switch (aEditSubAction
) {
94 case EditSubAction::eDeleteSelectedContent
:
95 case EditSubAction::eInsertLineBreak
:
96 case EditSubAction::eInsertParagraphSeparator
:
97 case EditSubAction::eCreateOrChangeList
:
98 case EditSubAction::eIndent
:
99 case EditSubAction::eOutdent
:
100 case EditSubAction::eSetOrClearAlignment
:
101 case EditSubAction::eCreateOrRemoveBlock
:
102 case EditSubAction::eMergeBlockContents
:
103 case EditSubAction::eRemoveList
:
104 case EditSubAction::eCreateOrChangeDefinitionListItem
:
105 case EditSubAction::eInsertElement
:
106 case EditSubAction::eInsertQuotation
:
107 case EditSubAction::eInsertQuotedText
:
114 template void HTMLEditor::SelectBRElementIfCollapsedInEmptyBlock(
115 EditorDOMPoint
& aStartPoint
, EditorDOMPoint
& aEndPoint
,
116 const Element
& aEditingHost
) const;
117 template void HTMLEditor::SelectBRElementIfCollapsedInEmptyBlock(
118 EditorRawDOMPoint
& aStartPoint
, EditorDOMPoint
& aEndPoint
,
119 const Element
& aEditingHost
) const;
120 template void HTMLEditor::SelectBRElementIfCollapsedInEmptyBlock(
121 EditorDOMPoint
& aStartPoint
, EditorRawDOMPoint
& aEndPoint
,
122 const Element
& aEditingHost
) const;
123 template void HTMLEditor::SelectBRElementIfCollapsedInEmptyBlock(
124 EditorRawDOMPoint
& aStartPoint
, EditorRawDOMPoint
& aEndPoint
,
125 const Element
& aEditingHost
) const;
126 template already_AddRefed
<nsRange
>
127 HTMLEditor::CreateRangeIncludingAdjuscentWhiteSpaces(
128 const EditorDOMRange
& aRange
);
129 template already_AddRefed
<nsRange
>
130 HTMLEditor::CreateRangeIncludingAdjuscentWhiteSpaces(
131 const EditorRawDOMRange
& aRange
);
132 template already_AddRefed
<nsRange
>
133 HTMLEditor::CreateRangeIncludingAdjuscentWhiteSpaces(
134 const EditorDOMPoint
& aStartPoint
, const EditorDOMPoint
& aEndPoint
);
135 template already_AddRefed
<nsRange
>
136 HTMLEditor::CreateRangeIncludingAdjuscentWhiteSpaces(
137 const EditorRawDOMPoint
& aStartPoint
, const EditorDOMPoint
& aEndPoint
);
138 template already_AddRefed
<nsRange
>
139 HTMLEditor::CreateRangeIncludingAdjuscentWhiteSpaces(
140 const EditorDOMPoint
& aStartPoint
, const EditorRawDOMPoint
& aEndPoint
);
141 template already_AddRefed
<nsRange
>
142 HTMLEditor::CreateRangeIncludingAdjuscentWhiteSpaces(
143 const EditorRawDOMPoint
& aStartPoint
, const EditorRawDOMPoint
& aEndPoint
);
144 template already_AddRefed
<nsRange
>
145 HTMLEditor::CreateRangeExtendedToHardLineStartAndEnd(
146 const EditorDOMRange
& aRange
, EditSubAction aEditSubAction
) const;
147 template already_AddRefed
<nsRange
>
148 HTMLEditor::CreateRangeExtendedToHardLineStartAndEnd(
149 const EditorRawDOMRange
& aRange
, EditSubAction aEditSubAction
) const;
150 template already_AddRefed
<nsRange
>
151 HTMLEditor::CreateRangeExtendedToHardLineStartAndEnd(
152 const EditorDOMPoint
& aStartPoint
, const EditorDOMPoint
& aEndPoint
,
153 EditSubAction aEditSubAction
) const;
154 template already_AddRefed
<nsRange
>
155 HTMLEditor::CreateRangeExtendedToHardLineStartAndEnd(
156 const EditorRawDOMPoint
& aStartPoint
, const EditorDOMPoint
& aEndPoint
,
157 EditSubAction aEditSubAction
) const;
158 template already_AddRefed
<nsRange
>
159 HTMLEditor::CreateRangeExtendedToHardLineStartAndEnd(
160 const EditorDOMPoint
& aStartPoint
, const EditorRawDOMPoint
& aEndPoint
,
161 EditSubAction aEditSubAction
) const;
162 template already_AddRefed
<nsRange
>
163 HTMLEditor::CreateRangeExtendedToHardLineStartAndEnd(
164 const EditorRawDOMPoint
& aStartPoint
, const EditorRawDOMPoint
& aEndPoint
,
165 EditSubAction aEditSubAction
) const;
166 template EditorDOMPoint
HTMLEditor::GetCurrentHardLineStartPoint(
167 const EditorDOMPoint
& aPoint
, EditSubAction aEditSubAction
,
168 const Element
& aEditingHost
) const;
169 template EditorDOMPoint
HTMLEditor::GetCurrentHardLineStartPoint(
170 const EditorRawDOMPoint
& aPoint
, EditSubAction aEditSubAction
,
171 const Element
& aEditingHost
) const;
172 template EditorDOMPoint
HTMLEditor::GetCurrentHardLineEndPoint(
173 const EditorDOMPoint
& aPoint
, const Element
& aEditingHost
) const;
174 template EditorDOMPoint
HTMLEditor::GetCurrentHardLineEndPoint(
175 const EditorRawDOMPoint
& aPoint
, const Element
& aEditingHost
) const;
177 nsresult
HTMLEditor::InitEditorContentAndSelection() {
178 MOZ_ASSERT(IsEditActionDataAvailable());
180 nsresult rv
= EditorBase::InitEditorContentAndSelection();
182 NS_WARNING("EditorBase::InitEditorContentAndSelection() failed");
186 Element
* bodyOrDocumentElement
= GetRoot();
187 if (NS_WARN_IF(!bodyOrDocumentElement
&& !GetDocument())) {
188 return NS_ERROR_FAILURE
;
191 if (!bodyOrDocumentElement
) {
195 rv
= InsertBRElementToEmptyListItemsAndTableCellsInRange(
196 RawRangeBoundary(bodyOrDocumentElement
, 0u),
197 RawRangeBoundary(bodyOrDocumentElement
,
198 bodyOrDocumentElement
->GetChildCount()));
199 if (NS_WARN_IF(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
200 return NS_ERROR_EDITOR_DESTROYED
;
202 NS_WARNING_ASSERTION(
204 "HTMLEditor::InsertBRElementToEmptyListItemsAndTableCellsInRange() "
205 "failed, but ignored");
209 void HTMLEditor::OnStartToHandleTopLevelEditSubAction(
210 EditSubAction aTopLevelEditSubAction
,
211 nsIEditor::EDirection aDirectionOfTopLevelEditSubAction
, ErrorResult
& aRv
) {
212 MOZ_ASSERT(IsEditActionDataAvailable());
213 MOZ_ASSERT(!aRv
.Failed());
215 EditorBase::OnStartToHandleTopLevelEditSubAction(
216 aTopLevelEditSubAction
, aDirectionOfTopLevelEditSubAction
, aRv
);
218 MOZ_ASSERT(GetTopLevelEditSubAction() == aTopLevelEditSubAction
);
219 MOZ_ASSERT(GetDirectionOfTopLevelEditSubAction() ==
220 aDirectionOfTopLevelEditSubAction
);
222 if (NS_WARN_IF(Destroyed())) {
223 aRv
.Throw(NS_ERROR_EDITOR_DESTROYED
);
227 if (!mInitSucceeded
) {
228 return; // We should do nothing if we're being initialized.
231 NS_WARNING_ASSERTION(
233 "EditorBase::OnStartToHandleTopLevelEditSubAction() failed");
235 // Remember where our selection was before edit action took place:
236 const auto atCompositionStart
=
237 GetFirstIMESelectionStartPoint
<EditorRawDOMPoint
>();
238 if (atCompositionStart
.IsSet()) {
239 // If there is composition string, let's remember current composition
241 TopLevelEditSubActionDataRef().mSelectedRange
->StoreRange(
242 atCompositionStart
, GetLastIMESelectionEndPoint
<EditorRawDOMPoint
>());
244 // Get the selection location
245 // XXX This may occur so that I think that we shouldn't throw exception
247 if (NS_WARN_IF(!SelectionRef().RangeCount())) {
248 aRv
.Throw(NS_ERROR_UNEXPECTED
);
251 if (const nsRange
* range
= SelectionRef().GetRangeAt(0)) {
252 TopLevelEditSubActionDataRef().mSelectedRange
->StoreRange(*range
);
256 // Register with range updater to track this as we perturb the doc
257 RangeUpdaterRef().RegisterRangeItem(
258 *TopLevelEditSubActionDataRef().mSelectedRange
);
260 // Remember current inline styles for deletion and normal insertion ops
261 bool cacheInlineStyles
;
262 switch (aTopLevelEditSubAction
) {
263 case EditSubAction::eInsertText
:
264 case EditSubAction::eInsertTextComingFromIME
:
265 case EditSubAction::eDeleteSelectedContent
:
266 cacheInlineStyles
= true;
270 IsStyleCachePreservingSubAction(aTopLevelEditSubAction
);
273 if (cacheInlineStyles
) {
274 nsCOMPtr
<nsIContent
> containerContent
= nsIContent::FromNodeOrNull(
275 aDirectionOfTopLevelEditSubAction
== nsIEditor::eNext
276 ? TopLevelEditSubActionDataRef().mSelectedRange
->mEndContainer
277 : TopLevelEditSubActionDataRef().mSelectedRange
->mStartContainer
);
278 if (NS_WARN_IF(!containerContent
)) {
279 aRv
.Throw(NS_ERROR_FAILURE
);
282 nsresult rv
= CacheInlineStyles(*containerContent
);
284 NS_WARNING("HTMLEditor::CacheInlineStyles() failed");
290 // Stabilize the document against contenteditable count changes
291 Document
* document
= GetDocument();
292 if (NS_WARN_IF(!document
)) {
293 aRv
.Throw(NS_ERROR_FAILURE
);
296 if (document
->GetEditingState() == Document::EditingState::eContentEditable
) {
297 document
->ChangeContentEditableCount(nullptr, +1);
298 TopLevelEditSubActionDataRef().mRestoreContentEditableCount
= true;
301 // Check that selection is in subtree defined by body node
302 nsresult rv
= EnsureSelectionInBodyOrDocumentElement();
303 if (NS_WARN_IF(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
304 aRv
.Throw(NS_ERROR_EDITOR_DESTROYED
);
307 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
308 "HTMLEditor::EnsureSelectionInBodyOrDocumentElement() "
309 "failed, but ignored");
312 nsresult
HTMLEditor::OnEndHandlingTopLevelEditSubAction() {
313 MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable());
317 if (NS_WARN_IF(Destroyed())) {
318 rv
= NS_ERROR_EDITOR_DESTROYED
;
322 if (!mInitSucceeded
) {
323 rv
= NS_OK
; // We should do nothing if we're being initialized.
327 // Do all the tricky stuff
328 rv
= OnEndHandlingTopLevelEditSubActionInternal();
329 NS_WARNING_ASSERTION(
331 "HTMLEditor::OnEndHandlingTopLevelEditSubActionInternal() failied");
332 // Perhaps, we need to do the following jobs even if the editor has been
333 // destroyed since they adjust some states of HTML document but don't
334 // modify the DOM tree nor Selection.
336 // Free up selectionState range item
337 if (TopLevelEditSubActionDataRef().mSelectedRange
) {
338 RangeUpdaterRef().DropRangeItem(
339 *TopLevelEditSubActionDataRef().mSelectedRange
);
342 // Reset the contenteditable count to its previous value
343 if (TopLevelEditSubActionDataRef().mRestoreContentEditableCount
) {
344 Document
* document
= GetDocument();
345 if (NS_WARN_IF(!document
)) {
346 rv
= NS_ERROR_FAILURE
;
349 if (document
->GetEditingState() ==
350 Document::EditingState::eContentEditable
) {
351 document
->ChangeContentEditableCount(nullptr, -1);
356 DebugOnly
<nsresult
> rvIgnored
=
357 EditorBase::OnEndHandlingTopLevelEditSubAction();
358 NS_WARNING_ASSERTION(
359 NS_FAILED(rv
) || NS_SUCCEEDED(rvIgnored
),
360 "EditorBase::OnEndHandlingTopLevelEditSubAction() failed, but ignored");
361 MOZ_ASSERT(!GetTopLevelEditSubAction());
362 MOZ_ASSERT(GetDirectionOfTopLevelEditSubAction() == eNone
);
366 nsresult
HTMLEditor::OnEndHandlingTopLevelEditSubActionInternal() {
367 MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable());
369 nsresult rv
= EnsureSelectionInBodyOrDocumentElement();
370 if (NS_WARN_IF(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
371 return NS_ERROR_EDITOR_DESTROYED
;
373 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
374 "HTMLEditor::EnsureSelectionInBodyOrDocumentElement() "
375 "failed, but ignored");
377 switch (GetTopLevelEditSubAction()) {
378 case EditSubAction::eReplaceHeadWithHTMLSource
:
379 case EditSubAction::eCreatePaddingBRElementForEmptyEditor
:
385 if (TopLevelEditSubActionDataRef().mChangedRange
->IsPositioned() &&
386 GetTopLevelEditSubAction() != EditSubAction::eUndo
&&
387 GetTopLevelEditSubAction() != EditSubAction::eRedo
) {
388 // don't let any txns in here move the selection around behind our back.
389 // Note that this won't prevent explicit selection setting from working.
390 AutoTransactionsConserveSelection
dontChangeMySelection(*this);
393 EditorRawDOMRange
changedRange(
394 *TopLevelEditSubActionDataRef().mChangedRange
);
395 if (changedRange
.IsPositioned() &&
396 changedRange
.EnsureNotInNativeAnonymousSubtree()) {
397 switch (GetTopLevelEditSubAction()) {
398 case EditSubAction::eInsertText
:
399 case EditSubAction::eInsertTextComingFromIME
:
400 case EditSubAction::eInsertLineBreak
:
401 case EditSubAction::eInsertParagraphSeparator
:
402 case EditSubAction::eDeleteText
: {
403 // XXX We should investigate whether this is really needed because
404 // it seems that the following code does not handle the
406 RefPtr
<nsRange
> extendedChangedRange
=
407 CreateRangeIncludingAdjuscentWhiteSpaces(changedRange
);
408 if (extendedChangedRange
) {
409 MOZ_ASSERT(extendedChangedRange
->IsPositioned());
410 // Use extended range temporarily.
411 TopLevelEditSubActionDataRef().mChangedRange
=
412 std::move(extendedChangedRange
);
417 RefPtr
<nsRange
> extendedChangedRange
=
418 CreateRangeExtendedToHardLineStartAndEnd(
419 changedRange
, GetTopLevelEditSubAction());
420 if (extendedChangedRange
) {
421 MOZ_ASSERT(extendedChangedRange
->IsPositioned());
422 // Use extended range temporarily.
423 TopLevelEditSubActionDataRef().mChangedRange
=
424 std::move(extendedChangedRange
);
432 // if we did a ranged deletion or handling backspace key, make sure we have
433 // a place to put caret.
434 // Note we only want to do this if the overall operation was deletion,
435 // not if deletion was done along the way for
436 // EditSubAction::eInsertHTMLSource, EditSubAction::eInsertText, etc.
437 // That's why this is here rather than DeleteSelectionAsSubAction().
438 // However, we shouldn't insert <br> elements if we've already removed
439 // empty block parents because users may want to disappear the line by
441 // XXX We should make HandleDeleteSelection() store expected container
442 // for handling this here since we cannot trust current selection is
443 // collapsed at deleted point.
444 if (GetTopLevelEditSubAction() == EditSubAction::eDeleteSelectedContent
&&
445 TopLevelEditSubActionDataRef().mDidDeleteNonCollapsedRange
&&
446 !TopLevelEditSubActionDataRef().mDidDeleteEmptyParentBlocks
) {
447 const auto newCaretPosition
=
448 GetFirstSelectionStartPoint
<EditorDOMPoint
>();
449 if (!newCaretPosition
.IsSet()) {
450 NS_WARNING("There was no selection range");
451 return NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
;
453 nsresult rv
= InsertBRElementIfHardLineIsEmptyAndEndsWithBlockBoundary(
458 "InsertBRElementIfHardLineIsEmptyAndEndsWithBlockBoundary() "
464 // add in any needed <br>s, and remove any unneeded ones.
465 nsresult rv
= InsertBRElementToEmptyListItemsAndTableCellsInRange(
466 TopLevelEditSubActionDataRef().mChangedRange
->StartRef().AsRaw(),
467 TopLevelEditSubActionDataRef().mChangedRange
->EndRef().AsRaw());
468 if (NS_WARN_IF(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
469 return NS_ERROR_EDITOR_DESTROYED
;
471 NS_WARNING_ASSERTION(
473 "HTMLEditor::InsertBRElementToEmptyListItemsAndTableCellsInRange()"
474 " failed, but ignored");
476 // merge any adjacent text nodes
477 switch (GetTopLevelEditSubAction()) {
478 case EditSubAction::eInsertText
:
479 case EditSubAction::eInsertTextComingFromIME
:
482 nsresult rv
= CollapseAdjacentTextNodes(
483 MOZ_KnownLive(*TopLevelEditSubActionDataRef().mChangedRange
));
484 if (NS_WARN_IF(Destroyed())) {
485 return NS_ERROR_EDITOR_DESTROYED
;
488 NS_WARNING("HTMLEditor::CollapseAdjacentTextNodes() failed");
495 // Clean up any empty nodes in the changed range unless they are inserted
497 if (TopLevelEditSubActionDataRef().mNeedsToCleanUpEmptyElements
) {
498 nsresult rv
= RemoveEmptyNodesIn(
499 MOZ_KnownLive(*TopLevelEditSubActionDataRef().mChangedRange
));
501 NS_WARNING("HTMLEditor::RemoveEmptyNodesIn() failed");
506 // attempt to transform any unneeded nbsp's into spaces after doing various
508 switch (GetTopLevelEditSubAction()) {
509 case EditSubAction::eDeleteSelectedContent
:
510 if (TopLevelEditSubActionDataRef().mDidNormalizeWhitespaces
) {
514 case EditSubAction::eInsertText
:
515 case EditSubAction::eInsertTextComingFromIME
:
516 case EditSubAction::eInsertLineBreak
:
517 case EditSubAction::eInsertParagraphSeparator
:
518 case EditSubAction::ePasteHTMLContent
:
519 case EditSubAction::eInsertHTMLSource
: {
520 // TODO: Temporarily, WhiteSpaceVisibilityKeeper replaces ASCII
521 // white-spaces with NPSPs and then, we'll replace them with ASCII
522 // white-spaces here. We should avoid this overwriting things as
523 // far as possible because replacing characters in text nodes
524 // causes running mutation event listeners which are really
526 // Adjust end of composition string if there is composition string.
527 auto pointToAdjust
= GetLastIMESelectionEndPoint
<EditorDOMPoint
>();
528 if (!pointToAdjust
.IsInContentNode()) {
529 // Otherwise, adjust current selection start point.
530 pointToAdjust
= GetFirstSelectionStartPoint
<EditorDOMPoint
>();
531 if (NS_WARN_IF(!pointToAdjust
.IsInContentNode())) {
532 return NS_ERROR_FAILURE
;
535 if (EditorUtils::IsEditableContent(*pointToAdjust
.ContainerAsContent(),
537 AutoTrackDOMPoint
trackPointToAdjust(RangeUpdaterRef(),
540 WhiteSpaceVisibilityKeeper::NormalizeVisibleWhiteSpacesAt(
541 *this, pointToAdjust
);
544 "WhiteSpaceVisibilityKeeper::NormalizeVisibleWhiteSpacesAt() "
550 // also do this for original selection endpoints.
551 // XXX Hmm, if `NormalizeVisibleWhiteSpacesAt()` runs mutation event
552 // listener and that causes changing `mSelectedRange`, what we
554 if (NS_WARN_IF(!TopLevelEditSubActionDataRef()
555 .mSelectedRange
->IsPositioned())) {
556 return NS_ERROR_FAILURE
;
559 EditorDOMPoint atStart
=
560 TopLevelEditSubActionDataRef().mSelectedRange
->StartPoint();
561 if (atStart
!= pointToAdjust
&& atStart
.IsInContentNode() &&
562 EditorUtils::IsEditableContent(*atStart
.ContainerAsContent(),
564 AutoTrackDOMPoint
trackPointToAdjust(RangeUpdaterRef(),
566 AutoTrackDOMPoint
trackStartPoint(RangeUpdaterRef(), &atStart
);
568 WhiteSpaceVisibilityKeeper::NormalizeVisibleWhiteSpacesAt(
570 if (NS_WARN_IF(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
571 return NS_ERROR_EDITOR_DESTROYED
;
573 NS_WARNING_ASSERTION(
575 "WhiteSpaceVisibilityKeeper::NormalizeVisibleWhiteSpacesAt() "
576 "failed, but ignored");
578 // we only need to handle old selection endpoint if it was different
580 EditorDOMPoint atEnd
=
581 TopLevelEditSubActionDataRef().mSelectedRange
->EndPoint();
582 if (!TopLevelEditSubActionDataRef().mSelectedRange
->Collapsed() &&
583 atEnd
!= pointToAdjust
&& atEnd
!= atStart
&&
584 atEnd
.IsInContentNode() &&
585 EditorUtils::IsEditableContent(*atEnd
.ContainerAsContent(),
588 WhiteSpaceVisibilityKeeper::NormalizeVisibleWhiteSpacesAt(*this,
590 if (NS_WARN_IF(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
591 return NS_ERROR_EDITOR_DESTROYED
;
593 NS_WARNING_ASSERTION(
595 "WhiteSpaceVisibilityKeeper::NormalizeVisibleWhiteSpacesAt() "
596 "failed, but ignored");
604 // If we created a new block, make sure caret is in it.
605 if (TopLevelEditSubActionDataRef().mNewBlockElement
&&
606 SelectionRef().IsCollapsed()) {
607 nsresult rv
= EnsureCaretInBlockElement(
608 MOZ_KnownLive(*TopLevelEditSubActionDataRef().mNewBlockElement
));
609 if (NS_WARN_IF(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
610 return NS_ERROR_EDITOR_DESTROYED
;
612 NS_WARNING_ASSERTION(
614 "HTMLEditor::EnsureSelectionInBlockElement() failed, but ignored");
617 // Adjust selection for insert text, html paste, and delete actions if
618 // we haven't removed new empty blocks. Note that if empty block parents
619 // are removed, Selection should've been adjusted by the method which
621 if (!TopLevelEditSubActionDataRef().mDidDeleteEmptyParentBlocks
&&
622 SelectionRef().IsCollapsed()) {
623 switch (GetTopLevelEditSubAction()) {
624 case EditSubAction::eInsertText
:
625 case EditSubAction::eInsertTextComingFromIME
:
626 case EditSubAction::eDeleteSelectedContent
:
627 case EditSubAction::eInsertLineBreak
:
628 case EditSubAction::eInsertParagraphSeparator
:
629 case EditSubAction::ePasteHTMLContent
:
630 case EditSubAction::eInsertHTMLSource
:
631 // XXX AdjustCaretPositionAndEnsurePaddingBRElement() intentionally
632 // does not create padding `<br>` element for empty editor.
633 // Investigate which is better that whether this should does it
634 // or wait MaybeCreatePaddingBRElementForEmptyEditor().
635 rv
= AdjustCaretPositionAndEnsurePaddingBRElement(
636 GetDirectionOfTopLevelEditSubAction());
639 "HTMLEditor::AdjustCaretPositionAndEnsurePaddingBRElement() "
649 // check for any styles which were removed inappropriately
650 bool reapplyCachedStyle
;
651 switch (GetTopLevelEditSubAction()) {
652 case EditSubAction::eInsertText
:
653 case EditSubAction::eInsertTextComingFromIME
:
654 case EditSubAction::eDeleteSelectedContent
:
655 reapplyCachedStyle
= true;
659 IsStyleCachePreservingSubAction(GetTopLevelEditSubAction());
663 // If the selection is in empty inline HTML elements, we should delete
664 // them unless it's inserted intentionally.
665 if (mPlaceholderBatch
&&
666 TopLevelEditSubActionDataRef().mNeedsToCleanUpEmptyElements
&&
667 SelectionRef().IsCollapsed() && SelectionRef().GetFocusNode()) {
668 RefPtr
<Element
> mostDistantEmptyInlineAncestor
= nullptr;
669 for (Element
* ancestor
:
670 SelectionRef().GetFocusNode()->InclusiveAncestorsOfType
<Element
>()) {
671 if (!ancestor
->IsHTMLElement() ||
672 !HTMLEditUtils::IsRemovableFromParentNode(*ancestor
) ||
673 !HTMLEditUtils::IsEmptyInlineContent(*ancestor
)) {
676 mostDistantEmptyInlineAncestor
= ancestor
;
678 if (mostDistantEmptyInlineAncestor
) {
680 DeleteNodeWithTransaction(*mostDistantEmptyInlineAncestor
);
683 "EditorBase::DeleteNodeWithTransaction() failed at deleting "
684 "empty inline ancestors");
690 // But the cached inline styles should be restored from type-in-state later.
691 if (reapplyCachedStyle
) {
692 DebugOnly
<nsresult
> rvIgnored
= mTypeInState
->UpdateSelState(*this);
693 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored
),
694 "TypeInState::UpdateSelState() failed, but ignored");
695 rvIgnored
= ReapplyCachedStyles();
696 NS_WARNING_ASSERTION(
697 NS_SUCCEEDED(rvIgnored
),
698 "HTMLEditor::ReapplyCachedStyles() failed, but ignored");
699 TopLevelEditSubActionDataRef().mCachedInlineStyles
->Clear();
703 rv
= HandleInlineSpellCheck(
704 TopLevelEditSubActionDataRef().mSelectedRange
->StartPoint(),
705 TopLevelEditSubActionDataRef().mChangedRange
);
707 NS_WARNING("EditorBase::HandleInlineSpellCheck() failed");
712 // XXX Need to investigate when the padding <br> element is removed because
713 // I don't see the <br> element with testing manually. If it won't be
714 // used, we can get rid of this cost.
715 rv
= MaybeCreatePaddingBRElementForEmptyEditor();
718 "EditorBase::MaybeCreatePaddingBRElementForEmptyEditor() failed");
722 // adjust selection HINT if needed
723 if (!TopLevelEditSubActionDataRef().mDidExplicitlySetInterLine
&&
724 SelectionRef().IsCollapsed()) {
725 SetSelectionInterlinePosition();
731 EditActionResult
HTMLEditor::CanHandleHTMLEditSubAction() const {
732 MOZ_ASSERT(IsEditActionDataAvailable());
734 if (NS_WARN_IF(Destroyed())) {
735 return EditActionResult(NS_ERROR_EDITOR_DESTROYED
);
738 // If there is not selection ranges, we should ignore the result.
739 if (!SelectionRef().RangeCount()) {
740 return EditActionCanceled();
743 const nsRange
* range
= SelectionRef().GetRangeAt(0);
744 nsINode
* selStartNode
= range
->GetStartContainer();
745 if (NS_WARN_IF(!selStartNode
) || NS_WARN_IF(!selStartNode
->IsContent())) {
746 return EditActionResult(NS_ERROR_FAILURE
);
749 if (!HTMLEditUtils::IsSimplyEditableNode(*selStartNode
) ||
750 HTMLEditUtils::IsNonEditableReplacedContent(*selStartNode
->AsContent())) {
751 return EditActionCanceled();
754 nsINode
* selEndNode
= range
->GetEndContainer();
755 if (NS_WARN_IF(!selEndNode
) || NS_WARN_IF(!selEndNode
->IsContent())) {
756 return EditActionResult(NS_ERROR_FAILURE
);
759 if (selStartNode
== selEndNode
) {
760 return EditActionIgnored();
763 if (!HTMLEditUtils::IsSimplyEditableNode(*selEndNode
) ||
764 HTMLEditUtils::IsNonEditableReplacedContent(*selEndNode
->AsContent())) {
765 return EditActionCanceled();
768 // XXX What does it mean the common ancestor is editable? I have no idea.
769 // It should be in same (active) editing host, and even if it's editable,
770 // there may be non-editable contents in the range.
771 nsINode
* commonAncestor
= range
->GetClosestCommonInclusiveAncestor();
772 if (!commonAncestor
) {
774 "AbstractRange::GetClosestCommonInclusiveAncestor() returned nullptr");
775 return EditActionResult(NS_ERROR_FAILURE
);
777 return HTMLEditUtils::IsSimplyEditableNode(*commonAncestor
)
778 ? EditActionIgnored()
779 : EditActionCanceled();
782 MOZ_CAN_RUN_SCRIPT
static nsStaticAtom
& MarginPropertyAtomForIndent(
783 nsIContent
& aContent
) {
784 nsAutoString direction
;
785 DebugOnly
<nsresult
> rvIgnored
= CSSEditUtils::GetComputedProperty(
786 aContent
, *nsGkAtoms::direction
, direction
);
787 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored
),
788 "CSSEditUtils::GetComputedProperty(nsGkAtoms::direction)"
789 " failed, but ignored");
790 return direction
.EqualsLiteral("rtl") ? *nsGkAtoms::marginRight
791 : *nsGkAtoms::marginLeft
;
794 nsresult
HTMLEditor::EnsureCaretNotAfterInvisibleBRElement() {
795 MOZ_ASSERT(IsEditActionDataAvailable());
796 MOZ_ASSERT(SelectionRef().IsCollapsed());
798 // If we are after a padding `<br>` element for empty last line in the same
799 // block, then move selection to be before it
800 const nsRange
* firstRange
= SelectionRef().GetRangeAt(0);
801 if (NS_WARN_IF(!firstRange
)) {
802 return NS_ERROR_FAILURE
;
805 EditorRawDOMPoint
atSelectionStart(firstRange
->StartRef());
806 if (NS_WARN_IF(!atSelectionStart
.IsSet())) {
807 return NS_ERROR_FAILURE
;
809 MOZ_ASSERT(atSelectionStart
.IsSetAndValid());
811 if (!atSelectionStart
.IsInContentNode()) {
815 Element
* editingHost
= ComputeEditingHost();
818 "HTMLEditor::EnsureCaretNotAfterInvisibleBRElement() did nothing "
819 "because of no editing host");
823 nsIContent
* previousBRElement
=
824 HTMLEditUtils::GetPreviousContent(atSelectionStart
, {}, editingHost
);
825 if (!previousBRElement
|| !previousBRElement
->IsHTMLElement(nsGkAtoms::br
) ||
826 !previousBRElement
->GetParent() ||
827 !EditorUtils::IsEditableContent(*previousBRElement
->GetParent(),
829 !HTMLEditUtils::IsInvisibleBRElement(*previousBRElement
)) {
833 const RefPtr
<const Element
> blockElementAtSelectionStart
=
834 HTMLEditUtils::GetInclusiveAncestorElement(
835 *atSelectionStart
.ContainerAsContent(),
836 HTMLEditUtils::ClosestBlockElement
);
837 const RefPtr
<const Element
> parentBlockElementOfBRElement
=
838 HTMLEditUtils::GetAncestorElement(*previousBRElement
,
839 HTMLEditUtils::ClosestBlockElement
);
841 if (!blockElementAtSelectionStart
||
842 blockElementAtSelectionStart
!= parentBlockElementOfBRElement
) {
846 // If we are here then the selection is right after a padding <br>
847 // element for empty last line that is in the same block as the
848 // selection. We need to move the selection start to be before the
849 // padding <br> element.
850 EditorRawDOMPoint
atInvisibleBRElement(previousBRElement
);
851 nsresult rv
= CollapseSelectionTo(atInvisibleBRElement
);
852 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
853 "EditorBase::CollapseSelectionTo() failed");
857 nsresult
HTMLEditor::MaybeCreatePaddingBRElementForEmptyEditor() {
858 MOZ_ASSERT(IsEditActionDataAvailable());
860 if (mPaddingBRElementForEmptyEditor
) {
864 IgnoredErrorResult ignoredError
;
865 AutoEditSubActionNotifier
startToHandleEditSubAction(
866 *this, EditSubAction::eCreatePaddingBRElementForEmptyEditor
,
867 nsIEditor::eNone
, ignoredError
);
868 if (NS_WARN_IF(ignoredError
.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED
))) {
869 return ignoredError
.StealNSResult();
871 NS_WARNING_ASSERTION(
872 !ignoredError
.Failed(),
873 "TextEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
874 ignoredError
.SuppressException();
876 RefPtr
<Element
> rootElement
= GetRoot();
881 // Now we've got the body element. Iterate over the body element's children,
882 // looking for editable content. If no editable content is found, insert the
883 // padding <br> element.
884 EditorType editorType
= GetEditorType();
885 bool isRootEditable
=
886 EditorUtils::IsEditableContent(*rootElement
, editorType
);
887 for (nsIContent
* rootChild
= rootElement
->GetFirstChild(); rootChild
;
888 rootChild
= rootChild
->GetNextSibling()) {
889 if (EditorUtils::IsPaddingBRElementForEmptyEditor(*rootChild
) ||
891 EditorUtils::IsEditableContent(*rootChild
, editorType
) ||
892 HTMLEditUtils::IsBlockElement(*rootChild
)) {
897 // Skip adding the padding <br> element for empty editor if body
899 if (IsHTMLEditor() && !HTMLEditUtils::IsSimplyEditableNode(*rootElement
)) {
904 RefPtr
<Element
> newBRElement
= CreateHTMLContent(nsGkAtoms::br
);
905 if (NS_WARN_IF(Destroyed())) {
906 return NS_ERROR_EDITOR_DESTROYED
;
908 if (NS_WARN_IF(!newBRElement
)) {
909 return NS_ERROR_FAILURE
;
912 mPaddingBRElementForEmptyEditor
=
913 static_cast<HTMLBRElement
*>(newBRElement
.get());
915 // Give it a special attribute.
916 newBRElement
->SetFlags(NS_PADDING_FOR_EMPTY_EDITOR
);
918 // Put the node in the document.
919 CreateElementResult insertBRElementResult
=
920 InsertNodeWithTransaction
<Element
>(*newBRElement
,
921 EditorDOMPoint(rootElement
, 0u));
922 if (insertBRElementResult
.isErr()) {
923 NS_WARNING("EditorBase::InsertNodeWithTransaction() failed");
924 return insertBRElementResult
.unwrapErr();
928 insertBRElementResult
.IgnoreCaretPointSuggestion();
929 nsresult rv
= CollapseSelectionToStartOf(*rootElement
);
930 if (MOZ_UNLIKELY(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
932 "EditorBase::CollapseSelectionToStartOf() caused destroying the "
934 return NS_ERROR_EDITOR_DESTROYED
;
936 NS_WARNING_ASSERTION(
938 "EditorBase::CollapseSelectionToStartOf() failed, but ignored");
942 nsresult
HTMLEditor::EnsureNoPaddingBRElementForEmptyEditor() {
943 MOZ_ASSERT(IsEditActionDataAvailable());
945 if (!mPaddingBRElementForEmptyEditor
) {
949 // If we're an HTML editor, a mutation event listener may recreate padding
950 // <br> element for empty editor again during the call of
951 // DeleteNodeWithTransaction(). So, move it first.
952 RefPtr
<HTMLBRElement
> paddingBRElement(
953 std::move(mPaddingBRElementForEmptyEditor
));
954 nsresult rv
= DeleteNodeWithTransaction(*paddingBRElement
);
955 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
956 "EditorBase::DeleteNodeWithTransaction() failed");
960 nsresult
HTMLEditor::ReflectPaddingBRElementForEmptyEditor() {
961 if (NS_WARN_IF(!mRootElement
)) {
962 NS_WARNING("Failed to handle padding BR element due to no root element");
963 return NS_ERROR_FAILURE
;
965 // The idea here is to see if the magic empty node has suddenly reappeared. If
966 // it has, set our state so we remember it. There is a tradeoff between doing
967 // here and at redo, or doing it everywhere else that might care. Since undo
968 // and redo are relatively rare, it makes sense to take the (small)
969 // performance hit here.
970 nsIContent
* firstLeafChild
= HTMLEditUtils::GetFirstLeafContent(
971 *mRootElement
, {LeafNodeType::OnlyLeafNode
});
972 if (firstLeafChild
&&
973 EditorUtils::IsPaddingBRElementForEmptyEditor(*firstLeafChild
)) {
974 mPaddingBRElementForEmptyEditor
=
975 static_cast<HTMLBRElement
*>(firstLeafChild
);
977 mPaddingBRElementForEmptyEditor
= nullptr;
982 nsresult
HTMLEditor::PrepareInlineStylesForCaret() {
983 MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable());
984 MOZ_ASSERT(SelectionRef().IsCollapsed());
986 // XXX This method works with the top level edit sub-action, but this
987 // must be wrong if we are handling nested edit action.
989 if (TopLevelEditSubActionDataRef().mDidDeleteSelection
) {
990 switch (GetTopLevelEditSubAction()) {
991 case EditSubAction::eInsertText
:
992 case EditSubAction::eInsertTextComingFromIME
:
993 case EditSubAction::eDeleteSelectedContent
: {
994 nsresult rv
= ReapplyCachedStyles();
996 NS_WARNING("HTMLEditor::ReapplyCachedStyles() failed");
1005 // For most actions we want to clear the cached styles, but there are
1007 if (!IsStyleCachePreservingSubAction(GetTopLevelEditSubAction())) {
1008 TopLevelEditSubActionDataRef().mCachedInlineStyles
->Clear();
1013 EditActionResult
HTMLEditor::HandleInsertText(
1014 EditSubAction aEditSubAction
, const nsAString
& aInsertionString
,
1015 SelectionHandling aSelectionHandling
) {
1016 MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable());
1017 MOZ_ASSERT(aEditSubAction
== EditSubAction::eInsertText
||
1018 aEditSubAction
== EditSubAction::eInsertTextComingFromIME
);
1019 MOZ_ASSERT_IF(aSelectionHandling
== SelectionHandling::Ignore
,
1020 aEditSubAction
== EditSubAction::eInsertTextComingFromIME
);
1022 EditActionResult result
= CanHandleHTMLEditSubAction();
1023 if (result
.Failed() || result
.Canceled()) {
1024 NS_WARNING_ASSERTION(result
.Succeeded(),
1025 "HTMLEditor::CanHandleHTMLEditSubAction() failed");
1029 UndefineCaretBidiLevel();
1031 // If the selection isn't collapsed, delete it. Don't delete existing inline
1032 // tags, because we're hopefully going to insert text (bug 787432).
1033 if (!SelectionRef().IsCollapsed() &&
1034 aSelectionHandling
== SelectionHandling::Delete
) {
1036 DeleteSelectionAsSubAction(nsIEditor::eNone
, nsIEditor::eNoStrip
);
1037 if (NS_FAILED(rv
)) {
1039 "EditorBase::DeleteSelectionAsSubAction(nsIEditor::eNone, "
1040 "nsIEditor::eNoStrip) failed");
1041 return EditActionHandled(rv
);
1045 nsresult rv
= EnsureNoPaddingBRElementForEmptyEditor();
1046 if (NS_WARN_IF(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
1047 return EditActionHandled(NS_ERROR_EDITOR_DESTROYED
);
1049 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
1050 "EditorBase::EnsureNoPaddingBRElementForEmptyEditor() "
1051 "failed, but ignored");
1053 if (NS_SUCCEEDED(rv
) && SelectionRef().IsCollapsed()) {
1054 nsresult rv
= EnsureCaretNotAfterInvisibleBRElement();
1055 if (NS_WARN_IF(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
1056 return EditActionHandled(NS_ERROR_EDITOR_DESTROYED
);
1058 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
1059 "HTMLEditor::EnsureCaretNotAfterInvisibleBRElement() "
1060 "failed, but ignored");
1061 if (NS_SUCCEEDED(rv
)) {
1062 nsresult rv
= PrepareInlineStylesForCaret();
1063 if (NS_WARN_IF(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
1064 return EditActionHandled(NS_ERROR_EDITOR_DESTROYED
);
1066 NS_WARNING_ASSERTION(
1068 "HTMLEditor::PrepareInlineStylesForCaret() failed, but ignored");
1072 RefPtr
<Document
> document
= GetDocument();
1073 if (NS_WARN_IF(!document
)) {
1074 return EditActionHandled(NS_ERROR_FAILURE
);
1077 RefPtr
<const nsRange
> firstRange
= SelectionRef().GetRangeAt(0);
1078 if (NS_WARN_IF(!firstRange
)) {
1079 return EditActionHandled(NS_ERROR_FAILURE
);
1082 // for every property that is set, insert a new inline style node
1083 // XXX CreateStyleForInsertText() adjusts selection automatically, but
1084 // it should just return the insertion point instead.
1085 rv
= CreateStyleForInsertText(*firstRange
);
1086 if (NS_FAILED(rv
)) {
1087 NS_WARNING("HTMLEditor::CreateStyleForInsertText() failed");
1088 return EditActionHandled(rv
);
1091 firstRange
= SelectionRef().GetRangeAt(0);
1092 if (NS_WARN_IF(!firstRange
)) {
1093 return EditActionHandled(NS_ERROR_FAILURE
);
1096 EditorDOMPoint
pointToInsert(firstRange
->StartRef());
1097 if (NS_WARN_IF(!pointToInsert
.IsSet()) ||
1098 NS_WARN_IF(!pointToInsert
.IsInContentNode())) {
1099 return EditActionHandled(NS_ERROR_FAILURE
);
1101 MOZ_ASSERT(pointToInsert
.IsSetAndValid());
1103 // If the point is not in an element which can contain text nodes, climb up
1105 if (!pointToInsert
.IsInTextNode()) {
1106 Element
* editingHost
= ComputeEditingHost(GetDocument()->IsXMLDocument()
1107 ? LimitInBodyElement::No
1108 : LimitInBodyElement::Yes
);
1109 if (NS_WARN_IF(!editingHost
)) {
1110 return EditActionHandled(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
);
1112 while (!HTMLEditUtils::CanNodeContain(*pointToInsert
.GetContainer(),
1113 *nsGkAtoms::textTagName
)) {
1114 if (NS_WARN_IF(pointToInsert
.GetContainer() == editingHost
) ||
1115 NS_WARN_IF(!pointToInsert
.GetContainerParentAsContent())) {
1116 NS_WARNING("Selection start point couldn't have text nodes");
1117 return EditActionHandled(NS_ERROR_FAILURE
);
1119 pointToInsert
.Set(pointToInsert
.ContainerAsContent());
1123 if (aEditSubAction
== EditSubAction::eInsertTextComingFromIME
) {
1124 auto compositionStartPoint
=
1125 GetFirstIMESelectionStartPoint
<EditorDOMPoint
>();
1126 if (!compositionStartPoint
.IsSet()) {
1127 compositionStartPoint
= pointToInsert
;
1130 if (aInsertionString
.IsEmpty()) {
1131 // Right now the WhiteSpaceVisibilityKeeper code bails on empty strings,
1132 // but IME needs the InsertTextWithTransaction() call to still happen
1133 // since empty strings are meaningful there.
1134 Result
<EditorDOMPoint
, nsresult
> insertTextResult
=
1135 InsertTextWithTransaction(*document
, aInsertionString
,
1136 compositionStartPoint
);
1137 if (MOZ_UNLIKELY(insertTextResult
.isErr())) {
1138 NS_WARNING("HTMLEditor::InsertTextWithTransaction() failed");
1139 return EditActionResult(insertTextResult
.unwrapErr());
1141 return EditActionHandled();
1144 auto compositionEndPoint
= GetLastIMESelectionEndPoint
<EditorDOMPoint
>();
1145 if (!compositionEndPoint
.IsSet()) {
1146 compositionEndPoint
= compositionStartPoint
;
1148 Result
<EditorDOMPoint
, nsresult
> replaceTextResult
=
1149 WhiteSpaceVisibilityKeeper::ReplaceText(
1150 *this, aInsertionString
,
1151 EditorDOMRange(compositionStartPoint
, compositionEndPoint
));
1152 if (MOZ_UNLIKELY(replaceTextResult
.isErr())) {
1153 NS_WARNING("WhiteSpaceVisibilityKeeper::ReplaceText() failed");
1154 return EditActionHandled(replaceTextResult
.unwrapErr());
1157 compositionStartPoint
= GetFirstIMESelectionStartPoint
<EditorDOMPoint
>();
1158 compositionEndPoint
= GetLastIMESelectionEndPoint
<EditorDOMPoint
>();
1159 if (NS_WARN_IF(!compositionStartPoint
.IsSet()) ||
1160 NS_WARN_IF(!compositionEndPoint
.IsSet())) {
1161 // Mutation event listener has changed the DOM tree...
1162 return EditActionHandled();
1164 nsresult rv
= TopLevelEditSubActionDataRef().mChangedRange
->SetStartAndEnd(
1165 compositionStartPoint
.ToRawRangeBoundary(),
1166 compositionEndPoint
.ToRawRangeBoundary());
1167 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
), "nsRange::SetStartAndEnd() failed");
1168 return EditActionHandled(rv
);
1171 MOZ_ASSERT(aEditSubAction
== EditSubAction::eInsertText
);
1173 // find where we are
1174 EditorDOMPoint
currentPoint(pointToInsert
);
1176 // is our text going to be PREformatted?
1177 // We remember this so that we know how to handle tabs.
1178 const bool isWhiteSpaceCollapsible
= !EditorUtils::IsWhiteSpacePreformatted(
1179 *pointToInsert
.ContainerAsContent());
1181 // turn off the edit listener: we know how to
1182 // build the "doc changed range" ourselves, and it's
1183 // must faster to do it once here than to track all
1184 // the changes one at a time.
1185 AutoRestore
<bool> disableListener(
1186 EditSubActionDataRef().mAdjustChangedRangeFromListener
);
1187 EditSubActionDataRef().mAdjustChangedRangeFromListener
= false;
1189 // don't change my selection in subtransactions
1190 AutoTransactionsConserveSelection
dontChangeMySelection(*this);
1192 constexpr auto newlineStr
= NS_LITERAL_STRING_FROM_CSTRING(LFSTR
);
1195 AutoTrackDOMPoint
tracker(RangeUpdaterRef(), &pointToInsert
);
1197 // for efficiency, break out the pre case separately. This is because
1198 // its a lot cheaper to search the input string for only newlines than
1199 // it is to search for both tabs and newlines.
1200 if (!isWhiteSpaceCollapsible
|| IsInPlaintextMode()) {
1202 pos
< AssertedCast
<int32_t>(aInsertionString
.Length())) {
1203 int32_t oldPos
= pos
;
1205 pos
= aInsertionString
.FindChar(nsCRT::LF
, oldPos
);
1208 subStrLen
= pos
- oldPos
;
1209 // if first char is newline, then use just it
1214 subStrLen
= aInsertionString
.Length() - oldPos
;
1215 pos
= aInsertionString
.Length();
1218 nsDependentSubstring
subStr(aInsertionString
, oldPos
, subStrLen
);
1221 if (subStr
.Equals(newlineStr
)) {
1222 CreateElementResult insertBRElementResult
=
1223 InsertBRElement(WithTransaction::Yes
, currentPoint
);
1224 if (insertBRElementResult
.isErr()) {
1226 "HTMLEditor::InsertBRElement(WithTransaction::Yes) failed");
1227 return EditActionHandled(insertBRElementResult
.unwrapErr());
1229 // We don't want to update selection here because we've blocked
1230 // InsertNodeTransaction updating selection with
1231 // dontChangeMySelection.
1232 insertBRElementResult
.IgnoreCaretPointSuggestion();
1233 MOZ_ASSERT(!AllowsTransactionsToChangeSelection());
1236 RefPtr
<Element
> brElement
= insertBRElementResult
.UnwrapNewNode();
1237 if (brElement
->GetNextSibling()) {
1238 pointToInsert
.Set(brElement
->GetNextSibling());
1240 pointToInsert
.SetToEndOf(currentPoint
.GetContainer());
1242 // XXX In most cases, pointToInsert and currentPoint are same here.
1243 // But if the <br> element has been moved to different point by
1244 // mutation observer, those points become different.
1245 currentPoint
.SetAfter(brElement
);
1246 NS_WARNING_ASSERTION(currentPoint
.IsSet(),
1247 "Failed to set after the <br> element");
1248 NS_WARNING_ASSERTION(currentPoint
== pointToInsert
,
1249 "Perhaps, <br> element position has been moved "
1250 "to different point "
1251 "by mutation observer");
1253 Result
<EditorDOMPoint
, nsresult
> insertTextResult
=
1254 InsertTextWithTransaction(*document
, subStr
, currentPoint
);
1255 if (MOZ_UNLIKELY(insertTextResult
.isErr())) {
1256 NS_WARNING("HTMLEditor::InsertTextWithTransaction() failed");
1257 return EditActionHandled(insertTextResult
.unwrapErr());
1259 currentPoint
= insertTextResult
.inspect();
1260 pointToInsert
= insertTextResult
.unwrap();
1264 const RefPtr
<Element
> editingHost
=
1265 ComputeEditingHost(LimitInBodyElement::No
);
1266 if (NS_WARN_IF(!editingHost
)) {
1267 return EditActionHandled(NS_ERROR_FAILURE
);
1269 constexpr auto tabStr
= u
"\t"_ns
;
1270 constexpr auto spacesStr
= u
" "_ns
;
1271 char specialChars
[] = {TAB
, nsCRT::LF
, 0};
1272 nsAutoString
insertionString(aInsertionString
); // For FindCharInSet().
1274 pos
< AssertedCast
<int32_t>(insertionString
.Length())) {
1275 int32_t oldPos
= pos
;
1277 pos
= insertionString
.FindCharInSet(specialChars
, oldPos
);
1280 subStrLen
= pos
- oldPos
;
1281 // if first char is newline, then use just it
1286 subStrLen
= insertionString
.Length() - oldPos
;
1287 pos
= insertionString
.Length();
1290 nsDependentSubstring
subStr(insertionString
, oldPos
, subStrLen
);
1293 if (subStr
.Equals(tabStr
)) {
1294 Result
<EditorDOMPoint
, nsresult
> insertTextResult
=
1295 WhiteSpaceVisibilityKeeper::InsertText(*this, spacesStr
,
1297 if (MOZ_UNLIKELY(insertTextResult
.isErr())) {
1298 NS_WARNING("WhiteSpaceVisibilityKeeper::InsertText() failed");
1299 return EditActionHandled(insertTextResult
.unwrapErr());
1302 MOZ_ASSERT(insertTextResult
.inspect().IsSet());
1303 currentPoint
= insertTextResult
.inspect();
1304 pointToInsert
= insertTextResult
.unwrap();
1307 else if (subStr
.Equals(newlineStr
)) {
1308 CreateElementResult insertBRElementResult
=
1309 WhiteSpaceVisibilityKeeper::InsertBRElement(*this, currentPoint
,
1311 if (insertBRElementResult
.isErr()) {
1312 NS_WARNING("WhiteSpaceVisibilityKeeper::InsertBRElement() failed");
1313 return EditActionHandled(insertBRElementResult
.unwrapErr());
1315 // TODO: Some methods called for handling non-preformatted text use
1316 // ComputeEditingHost(). Therefore, they depend on the latest
1317 // selection. So we cannot skip updating selection here.
1318 nsresult rv
= insertBRElementResult
.SuggestCaretPointTo(
1319 *this, {SuggestCaret::OnlyIfHasSuggestion
,
1320 SuggestCaret::OnlyIfTransactionsAllowedToDoIt
,
1321 SuggestCaret::AndIgnoreTrivialError
});
1322 if (NS_FAILED(rv
)) {
1323 NS_WARNING("CareateElementResult::SuggestCaretPointTo() failed");
1324 return EditActionHandled(rv
);
1326 NS_WARNING_ASSERTION(
1327 rv
!= NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR
,
1328 "CreateElementResult::SuggestCaretPointTo() failed, but ignored");
1330 RefPtr
<Element
> newBRElement
= insertBRElementResult
.UnwrapNewNode();
1331 MOZ_DIAGNOSTIC_ASSERT(newBRElement
);
1332 if (newBRElement
->GetNextSibling()) {
1333 pointToInsert
.Set(newBRElement
->GetNextSibling());
1335 pointToInsert
.SetToEndOf(currentPoint
.GetContainer());
1337 currentPoint
.SetAfter(newBRElement
);
1338 NS_WARNING_ASSERTION(currentPoint
.IsSet(),
1339 "Failed to set after the new <br> element");
1340 // XXX If the newBRElement has been moved or removed by mutation
1341 // observer, we hit this assert. We need to check if
1342 // newBRElement is in expected point, though, we must have
1343 // a lot of same bugs...
1344 NS_WARNING_ASSERTION(
1345 currentPoint
== pointToInsert
,
1346 "Perhaps, newBRElement has been moved or removed unexpectedly");
1348 Result
<EditorDOMPoint
, nsresult
> insertTextResult
=
1349 WhiteSpaceVisibilityKeeper::InsertText(*this, subStr
,
1351 if (MOZ_UNLIKELY(insertTextResult
.isErr())) {
1352 NS_WARNING("WhiteSpaceVisibilityKeeper::InsertText() failed");
1353 return EditActionHandled(insertTextResult
.unwrapErr());
1355 MOZ_ASSERT(insertTextResult
.inspect().IsSet());
1356 currentPoint
= insertTextResult
.inspect();
1357 pointToInsert
= insertTextResult
.unwrap();
1362 // After this block, pointToInsert is updated by AutoTrackDOMPoint.
1365 if (currentPoint
.IsSet()) {
1366 currentPoint
.SetInterlinePosition(InterlinePosition::EndOfLine
);
1367 nsresult rv
= CollapseSelectionTo(currentPoint
);
1368 if (MOZ_UNLIKELY(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
1370 "EditorBase::CollapseSelectionTo() caused destroying the editor");
1371 return EditActionHandled(NS_ERROR_EDITOR_DESTROYED
);
1373 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
1374 "Selection::Collapse() failed, but ignored");
1376 // manually update the doc changed range so that AfterEdit will clean up
1377 // the correct portion of the document.
1378 rv
= TopLevelEditSubActionDataRef().mChangedRange
->SetStartAndEnd(
1379 pointToInsert
.ToRawRangeBoundary(), currentPoint
.ToRawRangeBoundary());
1380 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
), "nsRange::SetStartAndEnd() failed");
1381 return EditActionHandled(rv
);
1384 DebugOnly
<nsresult
> rvIgnored
=
1385 SelectionRef().SetInterlinePosition(InterlinePosition::EndOfLine
);
1386 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored
),
1387 "Selection::SetInterlinePosition(InterlinePosition::"
1388 "EndOfLine) failed, but ignored");
1389 rv
= TopLevelEditSubActionDataRef().mChangedRange
->CollapseTo(pointToInsert
);
1390 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
), "nsRange::CollapseTo() failed");
1391 return EditActionHandled(rv
);
1394 nsresult
HTMLEditor::InsertLineBreakAsSubAction() {
1395 MOZ_ASSERT(IsEditActionDataAvailable());
1396 MOZ_ASSERT(!IsSelectionRangeContainerNotContent());
1398 if (NS_WARN_IF(!mInitSucceeded
)) {
1399 return NS_ERROR_NOT_INITIALIZED
;
1402 EditActionResult result
= CanHandleHTMLEditSubAction();
1403 if (result
.Failed() || result
.Canceled()) {
1404 NS_WARNING_ASSERTION(result
.Succeeded(),
1405 "HTMLEditor::CanHandleHTMLEditSubAction() failed");
1409 // XXX This may be called by execCommand() with "insertLineBreak".
1410 // In such case, naming the transaction "TypingTxnName" is odd.
1411 AutoPlaceholderBatch
treatAsOneTransaction(*this, *nsGkAtoms::TypingTxnName
,
1412 ScrollSelectionIntoView::Yes
,
1415 // calling it text insertion to trigger moz br treatment by rules
1416 // XXX Why do we use EditSubAction::eInsertText here? Looks like
1417 // EditSubAction::eInsertLineBreak or EditSubAction::eInsertNode
1419 IgnoredErrorResult ignoredError
;
1420 AutoEditSubActionNotifier
startToHandleEditSubAction(
1421 *this, EditSubAction::eInsertText
, nsIEditor::eNext
, ignoredError
);
1422 if (NS_WARN_IF(ignoredError
.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED
))) {
1423 return ignoredError
.StealNSResult();
1425 NS_WARNING_ASSERTION(
1426 !ignoredError
.Failed(),
1427 "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
1429 UndefineCaretBidiLevel();
1431 // If the selection isn't collapsed, delete it.
1432 if (!SelectionRef().IsCollapsed()) {
1434 DeleteSelectionAsSubAction(nsIEditor::eNone
, nsIEditor::eStrip
);
1435 if (NS_FAILED(rv
)) {
1437 "EditorBase::DeleteSelectionAsSubAction(eNone, eStrip) failed");
1442 const nsRange
* firstRange
= SelectionRef().GetRangeAt(0);
1443 if (NS_WARN_IF(!firstRange
)) {
1444 return NS_ERROR_FAILURE
;
1447 EditorDOMPoint
atStartOfSelection(firstRange
->StartRef());
1448 if (NS_WARN_IF(!atStartOfSelection
.IsSet())) {
1449 return NS_ERROR_FAILURE
;
1451 MOZ_ASSERT(atStartOfSelection
.IsSetAndValid());
1453 RefPtr
<Element
> editingHost
= ComputeEditingHost();
1454 if (NS_WARN_IF(!editingHost
)) {
1455 return NS_ERROR_FAILURE
;
1458 // For backward compatibility, we should not insert a linefeed if
1459 // paragraph separator is set to "br" which is Gecko-specific mode.
1460 if (GetDefaultParagraphSeparator() == ParagraphSeparator::br
||
1461 !HTMLEditUtils::ShouldInsertLinefeedCharacter(atStartOfSelection
,
1463 CreateElementResult insertBRElementResult
= InsertBRElement(
1464 WithTransaction::Yes
, atStartOfSelection
, nsIEditor::eNext
);
1465 if (insertBRElementResult
.isErr()) {
1466 NS_WARNING("HTMLEditor::InsertBRElement(WithTransaction::Yes) failed");
1467 return insertBRElementResult
.unwrapErr();
1469 nsresult rv
= insertBRElementResult
.SuggestCaretPointTo(*this, {});
1470 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
1471 "CreateElementResult::SuggestCaretPointTo() failed");
1472 MOZ_ASSERT(insertBRElementResult
.GetNewNode());
1476 nsresult rv
= EnsureNoPaddingBRElementForEmptyEditor();
1477 if (NS_WARN_IF(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
1478 return NS_ERROR_EDITOR_DESTROYED
;
1480 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
1481 "EditorBase::EnsureNoPaddingBRElementForEmptyEditor() "
1482 "failed, but ignored");
1484 if (NS_SUCCEEDED(rv
) && SelectionRef().IsCollapsed()) {
1485 nsresult rv
= EnsureCaretNotAfterInvisibleBRElement();
1486 if (NS_WARN_IF(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
1487 return NS_ERROR_EDITOR_DESTROYED
;
1489 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
1490 "HTMLEditor::EnsureCaretNotAfterInvisibleBRElement() "
1491 "failed, but ignored");
1492 if (NS_SUCCEEDED(rv
)) {
1493 nsresult rv
= PrepareInlineStylesForCaret();
1494 if (NS_WARN_IF(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
1495 return NS_ERROR_EDITOR_DESTROYED
;
1497 NS_WARNING_ASSERTION(
1499 "HTMLEditor::PrepareInlineStylesForCaret() failed, but ignored");
1503 firstRange
= SelectionRef().GetRangeAt(0);
1504 if (NS_WARN_IF(!firstRange
)) {
1505 return NS_ERROR_FAILURE
;
1508 atStartOfSelection
= EditorDOMPoint(firstRange
->StartRef());
1509 if (NS_WARN_IF(!atStartOfSelection
.IsSet())) {
1510 return NS_ERROR_FAILURE
;
1512 MOZ_ASSERT(atStartOfSelection
.IsSetAndValid());
1514 // Do nothing if the node is read-only
1515 if (!HTMLEditUtils::IsSimplyEditableNode(
1516 *atStartOfSelection
.GetContainer())) {
1517 return NS_SUCCESS_DOM_NO_OPERATION
;
1520 rv
= HandleInsertLinefeed(atStartOfSelection
, *editingHost
);
1521 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
1522 "HTMLEditor::HandleInsertLinefeed() failed");
1526 EditActionResult
HTMLEditor::InsertParagraphSeparatorAsSubAction() {
1527 if (NS_WARN_IF(!mInitSucceeded
)) {
1528 return EditActionIgnored(NS_ERROR_NOT_INITIALIZED
);
1531 EditActionResult result
= CanHandleHTMLEditSubAction();
1532 if (result
.Failed() || result
.Canceled()) {
1533 NS_WARNING_ASSERTION(result
.Succeeded(),
1534 "HTMLEditor::CanHandleHTMLEditSubAction() failed");
1538 // XXX This may be called by execCommand() with "insertParagraph".
1539 // In such case, naming the transaction "TypingTxnName" is odd.
1540 AutoPlaceholderBatch
treatAsOneTransaction(*this, *nsGkAtoms::TypingTxnName
,
1541 ScrollSelectionIntoView::Yes
,
1544 IgnoredErrorResult ignoredError
;
1545 AutoEditSubActionNotifier
startToHandleEditSubAction(
1546 *this, EditSubAction::eInsertParagraphSeparator
, nsIEditor::eNext
,
1548 if (NS_WARN_IF(ignoredError
.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED
))) {
1549 return EditActionResult(ignoredError
.StealNSResult());
1551 NS_WARNING_ASSERTION(
1552 !ignoredError
.Failed(),
1553 "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
1555 UndefineCaretBidiLevel();
1557 // If the selection isn't collapsed, delete it.
1558 if (!SelectionRef().IsCollapsed()) {
1560 DeleteSelectionAsSubAction(nsIEditor::eNone
, nsIEditor::eStrip
);
1561 if (NS_FAILED(rv
)) {
1563 "EditorBase::DeleteSelectionAsSubAction(eNone, eStrip) failed");
1564 return EditActionIgnored(rv
);
1568 nsresult rv
= EnsureNoPaddingBRElementForEmptyEditor();
1569 if (NS_WARN_IF(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
1570 return EditActionIgnored(NS_ERROR_EDITOR_DESTROYED
);
1572 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
1573 "EditorBase::EnsureNoPaddingBRElementForEmptyEditor() "
1574 "failed, but ignored");
1576 if (NS_SUCCEEDED(rv
) && SelectionRef().IsCollapsed()) {
1577 nsresult rv
= EnsureCaretNotAfterInvisibleBRElement();
1578 if (NS_WARN_IF(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
1579 return EditActionIgnored(NS_ERROR_EDITOR_DESTROYED
);
1581 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
1582 "HTMLEditor::EnsureCaretNotAfterInvisibleBRElement() "
1583 "failed, but ignored");
1584 if (NS_SUCCEEDED(rv
)) {
1585 nsresult rv
= PrepareInlineStylesForCaret();
1586 if (NS_WARN_IF(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
1587 return EditActionIgnored(NS_ERROR_EDITOR_DESTROYED
);
1589 NS_WARNING_ASSERTION(
1591 "HTMLEditor::PrepareInlineStylesForCaret() failed, but ignored");
1595 RefPtr
<Element
> editingHost
= ComputeEditingHost();
1596 if (NS_WARN_IF(!editingHost
)) {
1597 return EditActionIgnored(NS_ERROR_FAILURE
);
1600 if (IsMailEditor()) {
1601 const auto pointToSplit
= GetFirstSelectionStartPoint
<EditorDOMPoint
>();
1602 if (NS_WARN_IF(!pointToSplit
.IsInContentNode())) {
1603 return EditActionIgnored(NS_ERROR_FAILURE
);
1606 if (RefPtr
<Element
> mailCiteElement
= GetMostDistantAncestorMailCiteElement(
1607 *pointToSplit
.ContainerAsContent())) {
1608 // Split any mailcites in the way. Should we abort this if we encounter
1609 // table cell boundaries?
1610 Result
<EditorDOMPoint
, nsresult
> atNewBRElementOrError
=
1611 HandleInsertParagraphInMailCiteElement(*mailCiteElement
, pointToSplit
,
1613 if (MOZ_UNLIKELY(atNewBRElementOrError
.isErr())) {
1615 "HTMLEditor::HandleInsertParagraphInMailCiteElement() failed");
1616 return EditActionHandled(atNewBRElementOrError
.unwrapErr());
1618 EditorDOMPoint pointToPutCaret
= atNewBRElementOrError
.unwrap();
1619 MOZ_ASSERT(pointToPutCaret
.IsSet());
1620 pointToPutCaret
.SetInterlinePosition(InterlinePosition::StartOfNextLine
);
1621 MOZ_ASSERT(pointToPutCaret
.GetChild());
1622 MOZ_ASSERT(pointToPutCaret
.GetChild()->IsHTMLElement(nsGkAtoms::br
));
1623 nsresult rv
= CollapseSelectionTo(pointToPutCaret
);
1624 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
1625 "EditorBase::CollapseSelectionTo() failed");
1626 return EditActionHandled(rv
);
1630 // Smart splitting rules
1631 const nsRange
* firstRange
= SelectionRef().GetRangeAt(0);
1632 if (NS_WARN_IF(!firstRange
)) {
1633 return EditActionIgnored(NS_ERROR_FAILURE
);
1636 EditorDOMPoint
atStartOfSelection(firstRange
->StartRef());
1637 if (NS_WARN_IF(!atStartOfSelection
.IsSet())) {
1638 return EditActionIgnored(NS_ERROR_FAILURE
);
1640 MOZ_ASSERT(atStartOfSelection
.IsSetAndValid());
1642 // Do nothing if the node is read-only
1643 if (!HTMLEditUtils::IsSimplyEditableNode(
1644 *atStartOfSelection
.GetContainer())) {
1645 return EditActionCanceled();
1648 // If the active editing host is an inline element, or if the active editing
1649 // host is the block parent itself and we're configured to use <br> as a
1650 // paragraph separator, just append a <br>.
1651 // If the editing host parent element is editable, it means that the editing
1652 // host must be a <body> element and the selection may be outside the body
1653 // element. If the selection is outside the editing host, we should not
1654 // insert new paragraph nor <br> element.
1655 // XXX Currently, we don't support editing outside <body> element, but Blink
1657 if (editingHost
->GetParentElement() &&
1658 HTMLEditUtils::IsSimplyEditableNode(*editingHost
->GetParentElement()) &&
1659 (!atStartOfSelection
.IsInContentNode() ||
1660 !nsContentUtils::ContentIsFlattenedTreeDescendantOf(
1661 atStartOfSelection
.ContainerAsContent(), editingHost
))) {
1662 return EditActionHandled(NS_ERROR_EDITOR_NO_EDITABLE_RANGE
);
1665 // Look for the nearest parent block. However, don't return error even if
1666 // there is no block parent here because in such case, i.e., editing host
1667 // is an inline element, we should insert <br> simply.
1668 RefPtr
<Element
> editableBlockElement
=
1669 atStartOfSelection
.IsInContentNode()
1670 ? HTMLEditUtils::GetInclusiveAncestorElement(
1671 *atStartOfSelection
.ContainerAsContent(),
1672 HTMLEditUtils::ClosestEditableBlockElement
)
1675 ParagraphSeparator separator
= GetDefaultParagraphSeparator();
1676 bool insertLineBreak
;
1677 // If there is no block parent in the editing host, i.e., the editing host
1678 // itself is also a non-block element, we should insert a line break.
1679 if (!editableBlockElement
) {
1680 // XXX Chromium checks if the CSS box of the editing host is a block.
1681 insertLineBreak
= true;
1683 // If the editable block element is not splittable, e.g., it's an editing
1684 // host, and the default paragraph separator is <br> or the element cannot
1685 // contain a <p> element, we should insert a <br> element.
1686 else if (!HTMLEditUtils::IsSplittableNode(*editableBlockElement
)) {
1688 separator
== ParagraphSeparator::br
||
1689 !HTMLEditUtils::CanElementContainParagraph(*editingHost
) ||
1690 HTMLEditUtils::ShouldInsertLinefeedCharacter(atStartOfSelection
,
1693 // If the nearest block parent is a single-line container declared in
1694 // the execCommand spec and not the editing host, we should separate the
1695 // block even if the default paragraph separator is <br> element.
1696 else if (HTMLEditUtils::IsSingleLineContainer(*editableBlockElement
)) {
1697 insertLineBreak
= false;
1699 // Otherwise, unless there is no block ancestor which can contain <p>
1700 // element, we shouldn't insert a line break here.
1702 insertLineBreak
= true;
1703 for (const Element
* editableBlockAncestor
= editableBlockElement
;
1704 editableBlockAncestor
&& insertLineBreak
;
1705 editableBlockAncestor
= HTMLEditUtils::GetAncestorElement(
1706 *editableBlockAncestor
,
1707 HTMLEditUtils::ClosestEditableBlockElement
)) {
1709 !HTMLEditUtils::CanElementContainParagraph(*editableBlockAncestor
);
1713 // If we cannot insert a <p>/<div> element at the selection, we should insert
1714 // a <br> element or a linefeed instead.
1715 if (insertLineBreak
) {
1716 // For backward compatibility, we should not insert a linefeed if
1717 // paragraph separator is set to "br" which is Gecko-specific mode.
1718 if (separator
!= ParagraphSeparator::br
&&
1719 HTMLEditUtils::ShouldInsertLinefeedCharacter(atStartOfSelection
,
1721 nsresult rv
= HandleInsertLinefeed(atStartOfSelection
, *editingHost
);
1722 if (NS_FAILED(rv
)) {
1723 NS_WARNING("HTMLEditor::HandleInsertLinefeed() failed");
1724 return EditActionResult(rv
);
1726 return EditActionHandled();
1729 CreateElementResult insertBRElementResult
=
1730 HandleInsertBRElement(atStartOfSelection
, *editingHost
);
1731 if (insertBRElementResult
.isErr()) {
1732 NS_WARNING("HTMLEditor::HandleInsertBRElement() failed");
1733 return EditActionHandled(insertBRElementResult
.unwrapErr());
1735 nsresult rv
= insertBRElementResult
.SuggestCaretPointTo(*this, {});
1736 if (NS_FAILED(rv
)) {
1737 NS_WARNING("CreateElementResult::SuggestCaretPointTo() failed");
1738 return EditActionHandled(rv
);
1740 return EditActionHandled();
1743 if (!HTMLEditUtils::IsSplittableNode(*editableBlockElement
) &&
1744 separator
!= ParagraphSeparator::br
) {
1745 // Insert a new block first
1746 MOZ_ASSERT(separator
== ParagraphSeparator::div
||
1747 separator
== ParagraphSeparator::p
);
1748 // FormatBlockContainerWithTransaction() creates AutoSelectionRestorer.
1749 // Therefore, even if it returns NS_OK, editor might have been destroyed
1750 // at restoring Selection.
1751 nsresult rv
= FormatBlockContainerWithTransaction(
1752 MOZ_KnownLive(HTMLEditor::ToParagraphSeparatorTagName(separator
)));
1753 if (NS_WARN_IF(rv
== NS_ERROR_EDITOR_DESTROYED
) ||
1754 NS_WARN_IF(Destroyed())) {
1755 return EditActionIgnored(NS_ERROR_EDITOR_DESTROYED
);
1757 // We warn on failure, but don't handle it, because it might be harmless.
1758 // Instead we just check that a new block was actually created.
1759 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
1760 "HTMLEditor::FormatBlockContainerWithTransaction() "
1761 "failed, but ignored");
1763 firstRange
= SelectionRef().GetRangeAt(0);
1764 if (NS_WARN_IF(!firstRange
)) {
1765 return EditActionIgnored(NS_ERROR_FAILURE
);
1768 atStartOfSelection
= firstRange
->StartRef();
1769 if (NS_WARN_IF(!atStartOfSelection
.IsInContentNode())) {
1770 return EditActionIgnored(NS_ERROR_FAILURE
);
1772 MOZ_ASSERT(atStartOfSelection
.IsSetAndValid());
1774 editableBlockElement
= HTMLEditUtils::GetInclusiveAncestorElement(
1775 *atStartOfSelection
.ContainerAsContent(),
1776 HTMLEditUtils::ClosestEditableBlockElement
);
1777 if (NS_WARN_IF(!editableBlockElement
)) {
1778 return EditActionIgnored(NS_ERROR_UNEXPECTED
);
1780 if (NS_WARN_IF(!HTMLEditUtils::IsSplittableNode(*editableBlockElement
))) {
1781 // Didn't create a new block for some reason, fall back to <br>
1782 CreateElementResult insertBRElementResult
=
1783 HandleInsertBRElement(atStartOfSelection
, *editingHost
);
1784 if (insertBRElementResult
.isErr()) {
1785 NS_WARNING("HTMLEditor::HandleInsertBRElement() failed");
1786 return EditActionResult(insertBRElementResult
.unwrapErr());
1788 nsresult rv
= insertBRElementResult
.SuggestCaretPointTo(*this, {});
1789 if (NS_FAILED(rv
)) {
1790 NS_WARNING("CreateElementResult::SuggestCaretPointTo() failed");
1791 return EditActionHandled(rv
);
1793 return EditActionHandled();
1795 // Now, mNewBlockElement is last created block element for wrapping inline
1796 // elements around the caret position and AfterEditInner() will move
1797 // caret into it. However, it may be different from block parent of
1798 // the caret position. E.g., FormatBlockContainerWithTransaction() may
1799 // wrap following inline elements of a <br> element which is next sibling
1800 // of container of the caret. So, we need to adjust mNewBlockElement here
1801 // for avoiding jumping caret to odd position.
1802 TopLevelEditSubActionDataRef().mNewBlockElement
= editableBlockElement
;
1805 // If block is empty, populate with br. (For example, imagine a div that
1806 // contains the word "text". The user selects "text" and types return.
1807 // "Text" is deleted leaving an empty block. We want to put in one br to
1808 // make block have a line. Then code further below will put in a second br.)
1809 if (HTMLEditUtils::IsEmptyBlockElement(
1810 *editableBlockElement
,
1811 {EmptyCheckOption::TreatSingleBRElementAsVisible
})) {
1812 AutoEditorDOMPointChildInvalidator
lockOffset(atStartOfSelection
);
1813 EditorDOMPoint endOfBlockParent
;
1814 endOfBlockParent
.SetToEndOf(editableBlockElement
);
1815 const CreateElementResult insertBRElementResult
=
1816 InsertBRElement(WithTransaction::Yes
, endOfBlockParent
);
1817 if (insertBRElementResult
.isErr()) {
1818 NS_WARNING("HTMLEditor::InsertBRElement(WithTransaction::Yes) failed");
1819 return EditActionIgnored(insertBRElementResult
.unwrapErr());
1821 // XXX Is this intentional selection change?
1822 nsresult rv
= insertBRElementResult
.SuggestCaretPointTo(
1823 *this, {SuggestCaret::OnlyIfHasSuggestion
,
1824 SuggestCaret::OnlyIfTransactionsAllowedToDoIt
,
1825 SuggestCaret::AndIgnoreTrivialError
});
1826 if (NS_FAILED(rv
)) {
1827 NS_WARNING("CreateElementResult::SuggestCaretPointTo() failed");
1828 return EditActionHandled(rv
);
1830 NS_WARNING_ASSERTION(
1831 rv
!= NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR
,
1832 "CreateElementResult::SuggestCaretPointTo() failed, but ignored");
1833 MOZ_ASSERT(insertBRElementResult
.GetNewNode());
1836 RefPtr
<Element
> maybeNonEditableListItem
=
1837 HTMLEditUtils::GetClosestAncestorListItemElement(*editableBlockElement
,
1839 if (maybeNonEditableListItem
&&
1840 HTMLEditUtils::IsSplittableNode(*maybeNonEditableListItem
)) {
1841 Result
<EditorDOMPoint
, nsresult
> pointToPutCaretOrError
=
1842 HandleInsertParagraphInListItemElement(
1843 *maybeNonEditableListItem
, atStartOfSelection
, *editingHost
);
1844 if (MOZ_UNLIKELY(pointToPutCaretOrError
.isErr())) {
1845 if (NS_WARN_IF(pointToPutCaretOrError
.unwrapErr() ==
1846 NS_ERROR_EDITOR_DESTROYED
)) {
1847 return EditActionHandled(NS_ERROR_EDITOR_DESTROYED
);
1850 "HTMLEditor::HandleInsertParagraphInListItemElement() failed, but "
1852 return EditActionHandled();
1854 nsresult rv
= CollapseSelectionTo(pointToPutCaretOrError
.unwrap());
1855 if (MOZ_UNLIKELY(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
1857 "EditorBase::CollapseSelectionTo() caused destroying the editor");
1858 return EditActionHandled(NS_ERROR_EDITOR_DESTROYED
);
1860 NS_WARNING_ASSERTION(
1862 "EditorBase::CollapseSelectionTo() failed, but ignored");
1863 return EditActionHandled();
1866 if (HTMLEditUtils::IsHeader(*editableBlockElement
)) {
1867 // Headers: close (or split) header
1868 Result
<EditorDOMPoint
, nsresult
> pointToPutCaretOrError
=
1869 HandleInsertParagraphInHeadingElement(*editableBlockElement
,
1870 atStartOfSelection
);
1871 if (MOZ_UNLIKELY(pointToPutCaretOrError
.isErr())) {
1872 if (NS_WARN_IF(pointToPutCaretOrError
.unwrapErr() ==
1873 NS_ERROR_EDITOR_DESTROYED
)) {
1874 return EditActionHandled(NS_ERROR_EDITOR_DESTROYED
);
1877 "HTMLEditor::HandleInsertParagraphInHeadingElement() failed, but "
1879 return EditActionHandled();
1881 nsresult rv
= CollapseSelectionTo(pointToPutCaretOrError
.unwrap());
1882 if (MOZ_UNLIKELY(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
1884 "EditorBase::CollapseSelectionTo() caused destroying the editor");
1885 return EditActionHandled(NS_ERROR_EDITOR_DESTROYED
);
1887 NS_WARNING_ASSERTION(
1889 "EditorBase::CollapseSelectionTo() failed, but ignored");
1890 return EditActionHandled();
1893 // XXX Ideally, we should take same behavior with both <p> container and
1894 // <div> container. However, we are still using <br> as default
1895 // paragraph separator (non-standard) and we've split only <p> container
1896 // long time. Therefore, some web apps may depend on this behavior like
1897 // Gmail. So, let's use traditional odd behavior only when the default
1898 // paragraph separator is <br>. Otherwise, take consistent behavior
1899 // between <p> container and <div> container.
1900 if ((separator
== ParagraphSeparator::br
&&
1901 editableBlockElement
->IsHTMLElement(nsGkAtoms::p
)) ||
1902 (separator
!= ParagraphSeparator::br
&&
1903 editableBlockElement
->IsAnyOfHTMLElements(nsGkAtoms::p
,
1905 AutoEditorDOMPointChildInvalidator
lockOffset(atStartOfSelection
);
1906 // Paragraphs: special rules to look for <br>s
1907 EditActionResult result
=
1908 HandleInsertParagraphInParagraph(*editableBlockElement
);
1909 if (result
.Failed()) {
1910 NS_WARNING("HTMLEditor::HandleInsertParagraphInParagraph() failed");
1913 if (result
.Handled()) {
1914 // Now, atStartOfSelection may be invalid because the left paragraph
1915 // may have less children than its offset. For avoiding warnings of
1916 // validation of EditorDOMPoint, we should not touch it anymore.
1917 lockOffset
.Cancel();
1920 // Fall through, if HandleInsertParagraphInParagraph() didn't handle it.
1921 MOZ_ASSERT(!result
.Canceled(),
1922 "HandleInsertParagraphInParagraph() canceled this edit action, "
1923 "InsertParagraphSeparatorAsSubAction() needs to handle this "
1927 // If nobody handles this edit action, let's insert new <br> at the selection.
1928 CreateElementResult insertBRElementResult
=
1929 HandleInsertBRElement(atStartOfSelection
, *editingHost
);
1930 if (insertBRElementResult
.isErr()) {
1931 NS_WARNING("HTMLEditor::HandleInsertBRElement() failed");
1932 return EditActionIgnored(insertBRElementResult
.unwrapErr());
1934 rv
= insertBRElementResult
.SuggestCaretPointTo(*this, {});
1935 if (NS_FAILED(rv
)) {
1936 NS_WARNING("CreateElementResult::SuggestCaretPointTo() failed");
1937 return EditActionHandled(rv
);
1939 return EditActionHandled();
1942 CreateElementResult
HTMLEditor::HandleInsertBRElement(
1943 const EditorDOMPoint
& aPointToBreak
, Element
& aEditingHost
) {
1944 MOZ_ASSERT(aPointToBreak
.IsSet());
1945 MOZ_ASSERT(IsEditActionDataAvailable());
1947 bool brElementIsAfterBlock
= false, brElementIsBeforeBlock
= false;
1949 // First, insert a <br> element.
1950 RefPtr
<Element
> brElement
;
1951 if (IsInPlaintextMode()) {
1952 CreateElementResult insertBRElementResult
=
1953 InsertBRElement(WithTransaction::Yes
, aPointToBreak
);
1954 if (insertBRElementResult
.isErr()) {
1955 NS_WARNING("HTMLEditor::InsertBRElement(WithTransaction::Yes) failed");
1956 return CreateElementResult(insertBRElementResult
.unwrapErr());
1958 // We'll return with suggesting new caret position and nobody refers
1959 // selection after here. So we don't need to update selection here.
1960 insertBRElementResult
.IgnoreCaretPointSuggestion();
1961 MOZ_ASSERT(insertBRElementResult
.GetNewNode());
1962 brElement
= insertBRElementResult
.UnwrapNewNode();
1964 EditorDOMPoint
pointToBreak(aPointToBreak
);
1965 WSRunScanner
wsRunScanner(&aEditingHost
, pointToBreak
);
1966 WSScanResult backwardScanResult
=
1967 wsRunScanner
.ScanPreviousVisibleNodeOrBlockBoundaryFrom(pointToBreak
);
1968 if (MOZ_UNLIKELY(backwardScanResult
.Failed())) {
1970 "WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundaryFrom() failed");
1971 return CreateElementResult(NS_ERROR_FAILURE
);
1973 brElementIsAfterBlock
= backwardScanResult
.ReachedBlockBoundary();
1974 WSScanResult forwardScanResult
=
1975 wsRunScanner
.ScanNextVisibleNodeOrBlockBoundaryFrom(pointToBreak
);
1976 if (MOZ_UNLIKELY(forwardScanResult
.Failed())) {
1978 "WSRunScanner::ScanNextVisibleNodeOrBlockBoundaryFrom() failed");
1979 return CreateElementResult(NS_ERROR_FAILURE
);
1981 brElementIsBeforeBlock
= forwardScanResult
.ReachedBlockBoundary();
1982 // If the container of the break is a link, we need to split it and
1983 // insert new <br> between the split links.
1984 RefPtr
<Element
> linkNode
=
1985 HTMLEditor::GetLinkElement(pointToBreak
.GetContainer());
1987 const SplitNodeResult splitLinkNodeResult
= SplitNodeDeepWithTransaction(
1988 *linkNode
, pointToBreak
, SplitAtEdges::eDoNotCreateEmptyContainer
);
1989 if (splitLinkNodeResult
.isErr()) {
1991 "HTMLEditor::SplitNodeDeepWithTransaction(SplitAtEdges::"
1992 "eDoNotCreateEmptyContainer) failed");
1993 return CreateElementResult(splitLinkNodeResult
.unwrapErr());
1995 // TODO: Some methods called by
1996 // WhiteSpaceVisibilityKeeper::InsertBRElement() use
1997 // ComputeEditingHost() which depends on selection. Therefore,
1998 // we cannot skip updating selection here.
1999 nsresult rv
= splitLinkNodeResult
.SuggestCaretPointTo(
2000 *this, {SuggestCaret::OnlyIfHasSuggestion
,
2001 SuggestCaret::OnlyIfTransactionsAllowedToDoIt
});
2002 if (NS_FAILED(rv
)) {
2003 NS_WARNING("SplitNodeResult::SuggestCaretPointTo() failed");
2004 return CreateElementResult(rv
);
2006 pointToBreak
= splitLinkNodeResult
.AtSplitPoint
<EditorDOMPoint
>();
2007 // When adding caret suggestion to SplitNodeResult, here didn't change
2008 // selection so that just ignore it.
2009 splitLinkNodeResult
.IgnoreCaretPointSuggestion();
2011 CreateElementResult insertBRElementResult
=
2012 WhiteSpaceVisibilityKeeper::InsertBRElement(*this, pointToBreak
,
2014 if (insertBRElementResult
.isErr()) {
2015 NS_WARNING("WhiteSpaceVisibilityKeeper::InsertBRElement() failed");
2016 return CreateElementResult(insertBRElementResult
.unwrapErr());
2018 // We'll return with suggesting new caret position and nobody refers
2019 // selection after here. So we don't need to update selection here.
2020 insertBRElementResult
.IgnoreCaretPointSuggestion();
2021 brElement
= insertBRElementResult
.UnwrapNewNode();
2022 MOZ_ASSERT(brElement
);
2025 if (MOZ_UNLIKELY(!brElement
->GetParentNode())) {
2026 NS_WARNING("Inserted <br> element was removed by the web app");
2027 return CreateElementResult(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
);
2030 if (brElementIsAfterBlock
&& brElementIsBeforeBlock
) {
2031 // We just placed a <br> between block boundaries. This is the one case
2032 // where we want the selection to be before the br we just placed, as the
2033 // br will be on a new line, rather than at end of prior line.
2034 // XXX brElementIsAfterBlock and brElementIsBeforeBlock were set before
2035 // modifying the DOM tree. So, now, the <br> element may not be
2037 return CreateElementResult(
2038 std::move(brElement
),
2039 EditorDOMPoint(brElement
, InterlinePosition::StartOfNextLine
));
2042 auto afterBRElement
= EditorDOMPoint::After(brElement
);
2043 WSScanResult forwardScanFromAfterBRElementResult
=
2044 WSRunScanner::ScanNextVisibleNodeOrBlockBoundary(&aEditingHost
,
2046 if (MOZ_UNLIKELY(forwardScanFromAfterBRElementResult
.Failed())) {
2047 NS_WARNING("WSRunScanner::ScanNextVisibleNodeOrBlockBoundary() failed");
2048 return CreateElementResult(NS_ERROR_FAILURE
);
2050 if (forwardScanFromAfterBRElementResult
.ReachedBRElement()) {
2051 // The next thing after the break we inserted is another break. Move the
2052 // second break to be the first break's sibling. This will prevent them
2053 // from being in different inline nodes, which would break
2054 // SetInterlinePosition(). It will also assure that if the user clicks
2055 // away and then clicks back on their new blank line, they will still get
2056 // the style from the line above.
2057 if (brElement
->GetNextSibling() !=
2058 forwardScanFromAfterBRElementResult
.BRElementPtr()) {
2059 MOZ_ASSERT(forwardScanFromAfterBRElementResult
.BRElementPtr());
2060 const MoveNodeResult moveBRElementResult
= MoveNodeWithTransaction(
2061 MOZ_KnownLive(*forwardScanFromAfterBRElementResult
.BRElementPtr()),
2063 if (moveBRElementResult
.isErr()) {
2064 NS_WARNING("HTMLEditor::MoveNodeWithTransaction() failed");
2065 return CreateElementResult(moveBRElementResult
.unwrapErr());
2067 nsresult rv
= moveBRElementResult
.SuggestCaretPointTo(
2068 *this, {SuggestCaret::OnlyIfHasSuggestion
,
2069 SuggestCaret::OnlyIfTransactionsAllowedToDoIt
,
2070 SuggestCaret::AndIgnoreTrivialError
});
2071 if (NS_FAILED(rv
)) {
2072 NS_WARNING("MoveNodeResult::SuggestCaretPointTo() failed");
2073 return CreateElementResult(rv
);
2075 NS_WARNING_ASSERTION(
2076 rv
!= NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR
,
2077 "MoveNodeResult::SuggestCaretPointTo() failed, but ignored");
2081 // We want the caret to stick to whatever is past the break. This is because
2082 // the break is on the same line we were on, but the next content will be on
2083 // the following line.
2085 // An exception to this is if the break has a next sibling that is a block
2086 // node. Then we stick to the left to avoid an uber caret.
2087 nsIContent
* nextSiblingOfBRElement
= brElement
->GetNextSibling();
2088 afterBRElement
.SetInterlinePosition(
2089 nextSiblingOfBRElement
&&
2090 HTMLEditUtils::IsBlockElement(*nextSiblingOfBRElement
)
2091 ? InterlinePosition::EndOfLine
2092 : InterlinePosition::StartOfNextLine
);
2093 return CreateElementResult(std::move(brElement
), afterBRElement
);
2096 nsresult
HTMLEditor::HandleInsertLinefeed(const EditorDOMPoint
& aPointToBreak
,
2097 Element
& aEditingHost
) {
2098 MOZ_ASSERT(IsEditActionDataAvailable());
2100 if (NS_WARN_IF(!aPointToBreak
.IsSet())) {
2101 return NS_ERROR_INVALID_ARG
;
2104 // TODO: The following code is duplicated from `HandleInsertText`. They
2105 // should be merged when we fix bug 92921.
2107 RefPtr
<const nsRange
> caretRange
=
2108 nsRange::Create(aPointToBreak
.ToRawRangeBoundary(),
2109 aPointToBreak
.ToRawRangeBoundary(), IgnoreErrors());
2110 if (NS_WARN_IF(!caretRange
)) {
2111 return NS_ERROR_FAILURE
;
2114 nsresult rv
= CreateStyleForInsertText(*caretRange
);
2115 if (NS_FAILED(rv
)) {
2116 NS_WARNING("HTMLEditor::CreateStyleForInsertText() failed");
2120 caretRange
= SelectionRef().GetRangeAt(0);
2121 if (NS_WARN_IF(!caretRange
)) {
2122 return NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
;
2125 EditorDOMPoint
pointToInsert(caretRange
->StartRef());
2126 if (NS_WARN_IF(!pointToInsert
.IsSet()) ||
2127 NS_WARN_IF(!pointToInsert
.IsInContentNode())) {
2128 return NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
;
2130 MOZ_ASSERT(pointToInsert
.IsSetAndValid());
2132 // The node may not be able to have a text node so that we need to check it
2134 if (!pointToInsert
.IsInTextNode() &&
2135 !HTMLEditUtils::CanNodeContain(*pointToInsert
.ContainerAsContent(),
2136 *nsGkAtoms::textTagName
)) {
2138 "HTMLEditor::HandleInsertLinefeed() couldn't insert a linefeed because "
2139 "the insertion position couldn't have text nodes");
2140 return NS_ERROR_EDITOR_NO_EDITABLE_RANGE
;
2143 RefPtr
<Document
> document
= GetDocument();
2144 MOZ_ASSERT(document
);
2145 if (NS_WARN_IF(!document
)) {
2146 return NS_ERROR_FAILURE
;
2149 AutoRestore
<bool> disableListener(
2150 EditSubActionDataRef().mAdjustChangedRangeFromListener
);
2151 EditSubActionDataRef().mAdjustChangedRangeFromListener
= false;
2152 AutoTransactionsConserveSelection
dontChangeMySelection(*this);
2153 EditorRawDOMPoint caretAfterInsert
;
2155 AutoTrackDOMPoint
trackingInsertingPosition(RangeUpdaterRef(),
2157 Result
<EditorDOMPoint
, nsresult
> insertTextResult
=
2158 InsertTextWithTransaction(*document
, u
"\n"_ns
, pointToInsert
);
2159 if (MOZ_UNLIKELY(insertTextResult
.isErr())) {
2160 NS_WARNING("HTMLEditor::InsertTextWithTransaction() failed");
2161 return insertTextResult
.unwrapErr();
2163 caretAfterInsert
= insertTextResult
.unwrap().To
<EditorRawDOMPoint
>();
2166 // Insert a padding <br> element at the end of the block element if there is
2167 // no content between the inserted linefeed and the following block boundary
2168 // to make sure that the last line is visible.
2169 // XXX Blink/WebKit inserts another linefeed character in this case. However,
2170 // for doing it, we need more work, e.g., updating serializer, deleting
2171 // unnecessary padding <br> element at modifying the last line.
2172 if (caretAfterInsert
.IsInContentNode() &&
2173 caretAfterInsert
.IsEndOfContainer()) {
2174 WSRunScanner
wsScannerAtCaret(&aEditingHost
, caretAfterInsert
);
2175 if (wsScannerAtCaret
.StartsFromPreformattedLineBreak() &&
2176 wsScannerAtCaret
.EndsByBlockBoundary() &&
2177 HTMLEditUtils::CanNodeContain(*wsScannerAtCaret
.GetEndReasonContent(),
2179 auto newCaretPosition
= caretAfterInsert
.To
<EditorDOMPoint
>();
2181 AutoTrackDOMPoint
trackingInsertedPosition(RangeUpdaterRef(),
2183 AutoTrackDOMPoint
trackingNewCaretPosition(RangeUpdaterRef(),
2185 CreateElementResult insertBRElementResult
=
2186 InsertBRElement(WithTransaction::Yes
, newCaretPosition
);
2187 if (insertBRElementResult
.isErr()) {
2189 "HTMLEditor::InsertBRElement(WithTransaction::Yes) failed");
2190 return insertBRElementResult
.unwrapErr();
2192 // We're tracking next caret position with newCaretPosition. Therefore,
2193 // we don't need to update selection here.
2194 insertBRElementResult
.IgnoreCaretPointSuggestion();
2195 MOZ_ASSERT(insertBRElementResult
.GetNewNode());
2197 caretAfterInsert
= newCaretPosition
.To
<EditorRawDOMPoint
>();
2201 // manually update the doc changed range so that
2202 // OnEndHandlingTopLevelEditSubActionInternal will clean up the correct
2203 // portion of the document.
2204 if (NS_WARN_IF(!caretAfterInsert
.IsSet())) {
2205 DebugOnly
<nsresult
> rvIgnored
=
2206 SelectionRef().SetInterlinePosition(InterlinePosition::EndOfLine
);
2207 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored
),
2208 "Selection::SetInterlinePosition(InterlinePosition::"
2209 "EndOfLine) failed, but ignored");
2210 if (NS_FAILED(TopLevelEditSubActionDataRef().mChangedRange
->CollapseTo(
2212 NS_WARNING("nsRange::CollapseTo() failed");
2213 return NS_ERROR_FAILURE
;
2215 // XXX Here is odd. We did mChangedRange->SetStartAndEnd(pointToInsert,
2216 // caretAfterInsert), but it always fail so that returning
2217 // NS_ERROR_FAILURE from here is the traditional behavior...
2218 return NS_ERROR_FAILURE
;
2221 if (MOZ_UNLIKELY(NS_FAILED(
2222 TopLevelEditSubActionDataRef().mChangedRange
->SetStartAndEnd(
2223 pointToInsert
.ToRawRangeBoundary(),
2224 caretAfterInsert
.ToRawRangeBoundary())))) {
2225 NS_WARNING("nsRange::SetStartAndEnd() failed");
2226 return NS_ERROR_FAILURE
;
2229 caretAfterInsert
.SetInterlinePosition(InterlinePosition::EndOfLine
);
2230 rv
= CollapseSelectionTo(caretAfterInsert
);
2231 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
2232 "EditorBase::CollapseSelectionTo() failed");
2236 Result
<EditorDOMPoint
, nsresult
>
2237 HTMLEditor::HandleInsertParagraphInMailCiteElement(
2238 Element
& aMailCiteElement
, const EditorDOMPoint
& aPointToSplit
,
2239 Element
& aEditingHost
) {
2240 MOZ_ASSERT(IsEditActionDataAvailable());
2241 MOZ_ASSERT(aPointToSplit
.IsSet());
2242 NS_ASSERTION(!HTMLEditUtils::IsEmptyNode(aMailCiteElement
),
2243 "The mail-cite element will be deleted, does it expected result "
2246 const SplitNodeResult splitCiteElementResult
= [&]() MOZ_CAN_RUN_SCRIPT
{
2247 EditorDOMPoint
pointToSplit(aPointToSplit
);
2249 // If our selection is just before a break, nudge it to be just after
2250 // it. This does two things for us. It saves us the trouble of having
2251 // to add a break here ourselves to preserve the "blockness" of the
2252 // inline span mailquote (in the inline case), and : it means the break
2253 // won't end up making an empty line that happens to be inside a
2254 // mailquote (in either inline or block case). The latter can confuse a
2255 // user if they click there and start typing, because being in the
2256 // mailquote may affect wrapping behavior, or font color, etc.
2257 WSScanResult forwardScanFromPointToSplitResult
=
2258 WSRunScanner::ScanNextVisibleNodeOrBlockBoundary(&aEditingHost
,
2260 if (forwardScanFromPointToSplitResult
.Failed()) {
2261 return SplitNodeResult(NS_ERROR_FAILURE
);
2263 // If selection start point is before a break and it's inside the
2264 // mailquote, let's split it after the visible node.
2265 if (forwardScanFromPointToSplitResult
.ReachedBRElement() &&
2266 forwardScanFromPointToSplitResult
.BRElementPtr() != &aMailCiteElement
&&
2267 aMailCiteElement
.Contains(
2268 forwardScanFromPointToSplitResult
.BRElementPtr())) {
2270 forwardScanFromPointToSplitResult
.PointAfterContent
<EditorDOMPoint
>();
2273 if (NS_WARN_IF(!pointToSplit
.GetContainerAsContent())) {
2274 return SplitNodeResult(NS_ERROR_FAILURE
);
2277 SplitNodeResult splitResult
=
2278 SplitNodeDeepWithTransaction(aMailCiteElement
, pointToSplit
,
2279 SplitAtEdges::eDoNotCreateEmptyContainer
);
2280 if (splitResult
.isErr()) {
2282 "HTMLEditor::SplitNodeDeepWithTransaction(aMailCiteElement, "
2283 "SplitAtEdges::eDoNotCreateEmptyContainer) failed");
2286 nsresult rv
= splitResult
.SuggestCaretPointTo(
2287 *this, {SuggestCaret::OnlyIfHasSuggestion
,
2288 SuggestCaret::OnlyIfTransactionsAllowedToDoIt
});
2289 if (NS_FAILED(rv
)) {
2290 NS_WARNING("SplitNodeResult::SuggestCaretPointTo() failed");
2291 return SplitNodeResult(rv
);
2295 if (splitCiteElementResult
.isErr()) {
2296 NS_WARNING("Failed to split a mail-cite element");
2297 return Err(splitCiteElementResult
.unwrapErr());
2299 // When adding caret suggestion to SplitNodeResult, here didn't change
2300 // selection so that just ignore it.
2301 splitCiteElementResult
.IgnoreCaretPointSuggestion();
2303 // Add an invisible <br> to the end of left cite node if it was a <span> of
2304 // style="display: block". This is important, since when serializing the cite
2305 // to plain text, the span which caused the visual break is discarded. So the
2306 // added <br> will guarantee that the serializer will insert a break where the
2308 // FYI: splitCiteElementResult grabs the previous node and the next node with
2309 // nsCOMPtr or EditorDOMPoint. So, it's safe to access leftCiteElement
2310 // and rightCiteElement even after changing the DOM tree and/or selection
2311 // even though it's raw pointer.
2312 Element
* const leftCiteElement
=
2313 Element::FromNodeOrNull(splitCiteElementResult
.GetPreviousContent());
2314 Element
* const rightCiteElement
=
2315 Element::FromNodeOrNull(splitCiteElementResult
.GetNextContent());
2316 if (leftCiteElement
&& leftCiteElement
->IsHTMLElement(nsGkAtoms::span
) &&
2317 // XXX Oh, this depends on layout information of new element, and it's
2318 // created by the hacky flush in DoSplitNode(). So we need to
2319 // redesign around this for bug 1710784.
2320 leftCiteElement
->GetPrimaryFrame() &&
2321 leftCiteElement
->GetPrimaryFrame()->IsBlockFrameOrSubclass()) {
2322 nsIContent
* lastChild
= leftCiteElement
->GetLastChild();
2323 if (lastChild
&& !lastChild
->IsHTMLElement(nsGkAtoms::br
)) {
2324 const CreateElementResult insertInvisibleBRElementResult
=
2325 InsertBRElement(WithTransaction::Yes
,
2326 EditorDOMPoint::AtEndOf(*leftCiteElement
));
2327 if (insertInvisibleBRElementResult
.isErr()) {
2328 NS_WARNING("HTMLEditor::InsertBRElement(WithTransaction::Yes) failed");
2329 return Err(insertInvisibleBRElementResult
.unwrapErr());
2331 // We don't need to update selection here because we'll do another
2332 // InsertBRElement call soon.
2333 insertInvisibleBRElementResult
.IgnoreCaretPointSuggestion();
2334 MOZ_ASSERT(insertInvisibleBRElementResult
.GetNewNode());
2338 // In most cases, <br> should be inserted after current cite. However, if
2339 // left cite hasn't been created because the split point was start of the
2340 // cite node, <br> should be inserted before the current cite.
2341 CreateElementResult insertBRElementResult
=
2342 InsertBRElement(WithTransaction::Yes
,
2343 splitCiteElementResult
.AtSplitPoint
<EditorDOMPoint
>());
2344 if (insertBRElementResult
.isErr()) {
2345 NS_WARNING("HTMLEditor::InsertBRElement(WithTransaction::Yes) failed");
2346 return Err(insertBRElementResult
.unwrapErr());
2348 // We'll return with suggesting caret position. Therefore, we don't need
2349 // to update selection here.
2350 insertBRElementResult
.IgnoreCaretPointSuggestion();
2351 MOZ_ASSERT(insertBRElementResult
.GetNewNode());
2353 // if aMailCiteElement wasn't a block, we might also want another break before
2354 // it. We need to examine the content both before the br we just added and
2355 // also just after it. If we don't have another br or block boundary
2356 // adjacent, then we will need a 2nd br added to achieve blank line that user
2358 if (HTMLEditUtils::IsInlineElement(aMailCiteElement
)) {
2359 nsresult rvOfInsertingBRElement
= [&]() MOZ_CAN_RUN_SCRIPT
{
2360 EditorDOMPoint
pointToCreateNewBRElement(
2361 insertBRElementResult
.GetNewNode());
2363 // XXX Cannot we replace this complicated check with just a call of
2364 // HTMLEditUtils::IsVisibleBRElement with
2365 // resultOfInsertingBRElement.inspect()?
2366 WSScanResult backwardScanFromPointToCreateNewBRElementResult
=
2367 WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary(
2368 &aEditingHost
, pointToCreateNewBRElement
);
2370 backwardScanFromPointToCreateNewBRElementResult
.Failed())) {
2372 "WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary() "
2374 return NS_ERROR_FAILURE
;
2376 if (!backwardScanFromPointToCreateNewBRElementResult
2377 .InVisibleOrCollapsibleCharacters() &&
2378 !backwardScanFromPointToCreateNewBRElementResult
2379 .ReachedSpecialContent()) {
2380 return NS_SUCCESS_DOM_NO_OPERATION
;
2382 WSScanResult forwardScanFromPointAfterNewBRElementResult
=
2383 WSRunScanner::ScanNextVisibleNodeOrBlockBoundary(
2385 EditorRawDOMPoint::After(pointToCreateNewBRElement
));
2386 if (MOZ_UNLIKELY(forwardScanFromPointAfterNewBRElementResult
.Failed())) {
2387 NS_WARNING("WSRunScanner::ScanNextVisibleNodeOrBlockBoundary() failed");
2388 return NS_ERROR_FAILURE
;
2390 if (!forwardScanFromPointAfterNewBRElementResult
2391 .InVisibleOrCollapsibleCharacters() &&
2392 !forwardScanFromPointAfterNewBRElementResult
2393 .ReachedSpecialContent() &&
2394 // In case we're at the very end.
2395 !forwardScanFromPointAfterNewBRElementResult
2396 .ReachedCurrentBlockBoundary()) {
2397 return NS_SUCCESS_DOM_NO_OPERATION
;
2399 CreateElementResult insertBRElementResult
=
2400 InsertBRElement(WithTransaction::Yes
, pointToCreateNewBRElement
);
2401 if (insertBRElementResult
.isErr()) {
2402 NS_WARNING("HTMLEditor::InsertBRElement(WithTransaction::Yes) failed");
2403 return insertBRElementResult
.unwrapErr();
2405 insertBRElementResult
.IgnoreCaretPointSuggestion();
2406 MOZ_ASSERT(insertBRElementResult
.GetNewNode());
2410 if (NS_FAILED(rvOfInsertingBRElement
)) {
2412 "Failed to insert additional <br> element before the inline right "
2413 "mail-cite element");
2414 return Err(rvOfInsertingBRElement
);
2418 if (leftCiteElement
&& HTMLEditUtils::IsEmptyNode(*leftCiteElement
)) {
2419 // MOZ_KnownLive(leftCiteElement) because it's grabbed by
2420 // splitCiteElementResult.
2421 nsresult rv
= DeleteNodeWithTransaction(MOZ_KnownLive(*leftCiteElement
));
2422 if (NS_FAILED(rv
)) {
2423 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
2428 if (rightCiteElement
&& HTMLEditUtils::IsEmptyNode(*rightCiteElement
)) {
2429 // MOZ_KnownLive(rightCiteElement) because it's grabbed by
2430 // splitCiteElementResult.
2431 nsresult rv
= DeleteNodeWithTransaction(MOZ_KnownLive(*rightCiteElement
));
2432 if (NS_FAILED(rv
)) {
2433 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
2438 if (MOZ_UNLIKELY(!insertBRElementResult
.GetNewNode()->GetParent())) {
2439 NS_WARNING("Inserted <br> shouldn't become an orphan node");
2440 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
);
2442 return EditorDOMPoint(insertBRElementResult
.GetNewNode());
2445 HTMLEditor::CharPointData
2446 HTMLEditor::GetPreviousCharPointDataForNormalizingWhiteSpaces(
2447 const EditorDOMPointInText
& aPoint
) const {
2448 MOZ_ASSERT(aPoint
.IsSetAndValid());
2450 if (!aPoint
.IsStartOfContainer()) {
2451 return CharPointData::InSameTextNode(
2452 HTMLEditor::GetPreviousCharPointType(aPoint
));
2454 const auto previousCharPoint
=
2455 WSRunScanner::GetPreviousEditableCharPoint
<EditorRawDOMPointInText
>(
2456 ComputeEditingHost(), aPoint
);
2457 if (!previousCharPoint
.IsSet()) {
2458 return CharPointData::InDifferentTextNode(CharPointType::TextEnd
);
2460 return CharPointData::InDifferentTextNode(
2461 HTMLEditor::GetCharPointType(previousCharPoint
));
2464 HTMLEditor::CharPointData
2465 HTMLEditor::GetInclusiveNextCharPointDataForNormalizingWhiteSpaces(
2466 const EditorDOMPointInText
& aPoint
) const {
2467 MOZ_ASSERT(aPoint
.IsSetAndValid());
2469 if (!aPoint
.IsEndOfContainer()) {
2470 return CharPointData::InSameTextNode(HTMLEditor::GetCharPointType(aPoint
));
2472 const auto nextCharPoint
=
2473 WSRunScanner::GetInclusiveNextEditableCharPoint
<EditorRawDOMPointInText
>(
2474 ComputeEditingHost(), aPoint
);
2475 if (!nextCharPoint
.IsSet()) {
2476 return CharPointData::InDifferentTextNode(CharPointType::TextEnd
);
2478 return CharPointData::InDifferentTextNode(
2479 HTMLEditor::GetCharPointType(nextCharPoint
));
2483 void HTMLEditor::GenerateWhiteSpaceSequence(
2484 nsAString
& aResult
, uint32_t aLength
,
2485 const CharPointData
& aPreviousCharPointData
,
2486 const CharPointData
& aNextCharPointData
) {
2487 MOZ_ASSERT(aResult
.IsEmpty());
2488 MOZ_ASSERT(aLength
);
2489 // For now, this method does not assume that result will be append to
2490 // white-space sequence in the text node.
2491 MOZ_ASSERT(aPreviousCharPointData
.AcrossTextNodeBoundary() ||
2492 !aPreviousCharPointData
.IsCollapsibleWhiteSpace());
2493 // For now, this method does not assume that the result will be inserted
2494 // into white-space sequence nor start of white-space sequence.
2495 MOZ_ASSERT(aNextCharPointData
.AcrossTextNodeBoundary() ||
2496 !aNextCharPointData
.IsCollapsibleWhiteSpace());
2499 // Even if previous/next char is in different text node, we should put
2500 // an ASCII white-space between visible characters.
2501 // XXX This means that this does not allow to put an NBSP in HTML editor
2502 // without preformatted style. However, Chrome has same issue too.
2503 if (aPreviousCharPointData
.Type() == CharPointType::VisibleChar
&&
2504 aNextCharPointData
.Type() == CharPointType::VisibleChar
) {
2505 aResult
.Assign(HTMLEditUtils::kSpace
);
2508 // If it's start or end of text, put an NBSP.
2509 if (aPreviousCharPointData
.Type() == CharPointType::TextEnd
||
2510 aNextCharPointData
.Type() == CharPointType::TextEnd
) {
2511 aResult
.Assign(HTMLEditUtils::kNBSP
);
2514 // If the character is next to a preformatted linefeed, we need to put
2515 // an NBSP for avoiding collapsed into the linefeed.
2516 if (aPreviousCharPointData
.Type() == CharPointType::PreformattedLineBreak
||
2517 aNextCharPointData
.Type() == CharPointType::PreformattedLineBreak
) {
2518 aResult
.Assign(HTMLEditUtils::kNBSP
);
2521 // Now, the white-space will be inserted to a white-space sequence, but not
2522 // end of text. We can put an ASCII white-space only when both sides are
2523 // not ASCII white-spaces.
2525 aPreviousCharPointData
.Type() == CharPointType::ASCIIWhiteSpace
||
2526 aNextCharPointData
.Type() == CharPointType::ASCIIWhiteSpace
2527 ? HTMLEditUtils::kNBSP
2528 : HTMLEditUtils::kSpace
);
2532 // Generate pairs of NBSP and ASCII white-space.
2533 aResult
.SetLength(aLength
);
2534 bool appendNBSP
= true; // Basically, starts with an NBSP.
2535 char16_t
* lastChar
= aResult
.EndWriting() - 1;
2536 for (char16_t
* iter
= aResult
.BeginWriting(); iter
!= lastChar
; iter
++) {
2537 *iter
= appendNBSP
? HTMLEditUtils::kNBSP
: HTMLEditUtils::kSpace
;
2538 appendNBSP
= !appendNBSP
;
2541 // If the final one is expected to an NBSP, we can put an NBSP simply.
2543 *lastChar
= HTMLEditUtils::kNBSP
;
2547 // If next char point is end of text node, an ASCII white-space or
2548 // preformatted linefeed, we need to put an NBSP.
2550 aNextCharPointData
.AcrossTextNodeBoundary() ||
2551 aNextCharPointData
.Type() == CharPointType::ASCIIWhiteSpace
||
2552 aNextCharPointData
.Type() == CharPointType::PreformattedLineBreak
2553 ? HTMLEditUtils::kNBSP
2554 : HTMLEditUtils::kSpace
;
2557 void HTMLEditor::ExtendRangeToDeleteWithNormalizingWhiteSpaces(
2558 EditorDOMPointInText
& aStartToDelete
, EditorDOMPointInText
& aEndToDelete
,
2559 nsAString
& aNormalizedWhiteSpacesInStartNode
,
2560 nsAString
& aNormalizedWhiteSpacesInEndNode
) const {
2561 MOZ_ASSERT(aStartToDelete
.IsSetAndValid());
2562 MOZ_ASSERT(aEndToDelete
.IsSetAndValid());
2563 MOZ_ASSERT(aStartToDelete
.EqualsOrIsBefore(aEndToDelete
));
2564 MOZ_ASSERT(aNormalizedWhiteSpacesInStartNode
.IsEmpty());
2565 MOZ_ASSERT(aNormalizedWhiteSpacesInEndNode
.IsEmpty());
2567 // First, check whether there is surrounding white-spaces or not, and if there
2568 // are, check whether they are collapsible or not. Note that we shouldn't
2569 // touch white-spaces in different text nodes for performance, but we need
2570 // adjacent text node's first or last character information in some cases.
2571 Element
* editingHost
= ComputeEditingHost();
2572 const EditorDOMPointInText precedingCharPoint
=
2573 WSRunScanner::GetPreviousEditableCharPoint(editingHost
, aStartToDelete
);
2574 const EditorDOMPointInText followingCharPoint
=
2575 WSRunScanner::GetInclusiveNextEditableCharPoint(editingHost
,
2577 // Blink-compat: Normalize white-spaces in first node only when not removing
2578 // its last character or no text nodes follow the first node.
2579 // If removing last character of first node and there are
2580 // following text nodes, white-spaces in following text node are
2581 // normalized instead.
2582 const bool removingLastCharOfStartNode
=
2583 aStartToDelete
.ContainerAsText() != aEndToDelete
.ContainerAsText() ||
2584 (aEndToDelete
.IsEndOfContainer() && followingCharPoint
.IsSet());
2585 const bool maybeNormalizePrecedingWhiteSpaces
=
2586 !removingLastCharOfStartNode
&& precedingCharPoint
.IsSet() &&
2587 !precedingCharPoint
.IsEndOfContainer() &&
2588 precedingCharPoint
.ContainerAsText() ==
2589 aStartToDelete
.ContainerAsText() &&
2590 precedingCharPoint
.IsCharCollapsibleASCIISpaceOrNBSP();
2591 const bool maybeNormalizeFollowingWhiteSpaces
=
2592 followingCharPoint
.IsSet() && !followingCharPoint
.IsEndOfContainer() &&
2593 (followingCharPoint
.ContainerAsText() == aEndToDelete
.ContainerAsText() ||
2594 removingLastCharOfStartNode
) &&
2595 followingCharPoint
.IsCharCollapsibleASCIISpaceOrNBSP();
2597 if (!maybeNormalizePrecedingWhiteSpaces
&&
2598 !maybeNormalizeFollowingWhiteSpaces
) {
2599 return; // There are no white-spaces.
2602 // Next, consider the range to normalize.
2603 EditorDOMPointInText startToNormalize
, endToNormalize
;
2604 if (maybeNormalizePrecedingWhiteSpaces
) {
2605 Maybe
<uint32_t> previousCharOffsetOfWhiteSpaces
=
2606 HTMLEditUtils::GetPreviousNonCollapsibleCharOffset(
2607 precedingCharPoint
, {WalkTextOption::TreatNBSPsCollapsible
});
2608 startToNormalize
.Set(precedingCharPoint
.ContainerAsText(),
2609 previousCharOffsetOfWhiteSpaces
.isSome()
2610 ? previousCharOffsetOfWhiteSpaces
.value() + 1
2612 MOZ_ASSERT(!startToNormalize
.IsEndOfContainer());
2614 if (maybeNormalizeFollowingWhiteSpaces
) {
2615 Maybe
<uint32_t> nextCharOffsetOfWhiteSpaces
=
2616 HTMLEditUtils::GetInclusiveNextNonCollapsibleCharOffset(
2617 followingCharPoint
, {WalkTextOption::TreatNBSPsCollapsible
});
2618 if (nextCharOffsetOfWhiteSpaces
.isSome()) {
2619 endToNormalize
.Set(followingCharPoint
.ContainerAsText(),
2620 nextCharOffsetOfWhiteSpaces
.value());
2622 endToNormalize
.SetToEndOf(followingCharPoint
.ContainerAsText());
2624 MOZ_ASSERT(!endToNormalize
.IsStartOfContainer());
2627 // Next, retrieve surrounding information of white-space sequence.
2628 // If we're removing first text node's last character, we need to
2629 // normalize white-spaces starts from another text node. In this case,
2630 // we need to lie for avoiding assertion in GenerateWhiteSpaceSequence().
2631 CharPointData previousCharPointData
=
2632 removingLastCharOfStartNode
2633 ? CharPointData::InDifferentTextNode(CharPointType::TextEnd
)
2634 : GetPreviousCharPointDataForNormalizingWhiteSpaces(
2635 startToNormalize
.IsSet() ? startToNormalize
: aStartToDelete
);
2636 CharPointData nextCharPointData
=
2637 GetInclusiveNextCharPointDataForNormalizingWhiteSpaces(
2638 endToNormalize
.IsSet() ? endToNormalize
: aEndToDelete
);
2640 // Next, compute number of white-spaces in start/end node.
2641 uint32_t lengthInStartNode
= 0, lengthInEndNode
= 0;
2642 if (startToNormalize
.IsSet()) {
2643 MOZ_ASSERT(startToNormalize
.ContainerAsText() ==
2644 aStartToDelete
.ContainerAsText());
2645 lengthInStartNode
= aStartToDelete
.Offset() - startToNormalize
.Offset();
2646 MOZ_ASSERT(lengthInStartNode
);
2648 if (endToNormalize
.IsSet()) {
2650 endToNormalize
.ContainerAsText() == aEndToDelete
.ContainerAsText()
2651 ? endToNormalize
.Offset() - aEndToDelete
.Offset()
2652 : endToNormalize
.Offset();
2653 MOZ_ASSERT(lengthInEndNode
);
2654 // If we normalize white-spaces in a text node, we can replace all of them
2655 // with one ReplaceTextTransaction.
2656 if (endToNormalize
.ContainerAsText() == aStartToDelete
.ContainerAsText()) {
2657 lengthInStartNode
+= lengthInEndNode
;
2658 lengthInEndNode
= 0;
2662 MOZ_ASSERT(lengthInStartNode
+ lengthInEndNode
);
2664 // Next, generate normalized white-spaces.
2665 if (!lengthInEndNode
) {
2666 HTMLEditor::GenerateWhiteSpaceSequence(
2667 aNormalizedWhiteSpacesInStartNode
, lengthInStartNode
,
2668 previousCharPointData
, nextCharPointData
);
2669 } else if (!lengthInStartNode
) {
2670 HTMLEditor::GenerateWhiteSpaceSequence(
2671 aNormalizedWhiteSpacesInEndNode
, lengthInEndNode
, previousCharPointData
,
2674 // For making `GenerateWhiteSpaceSequence()` simpler, we should create
2675 // whole white-space sequence first, then, copy to the out params.
2676 nsAutoString whiteSpaces
;
2677 HTMLEditor::GenerateWhiteSpaceSequence(
2678 whiteSpaces
, lengthInStartNode
+ lengthInEndNode
, previousCharPointData
,
2680 aNormalizedWhiteSpacesInStartNode
=
2681 Substring(whiteSpaces
, 0, lengthInStartNode
);
2682 aNormalizedWhiteSpacesInEndNode
= Substring(whiteSpaces
, lengthInStartNode
);
2683 MOZ_ASSERT(aNormalizedWhiteSpacesInEndNode
.Length() == lengthInEndNode
);
2686 // TODO: Shrink the replacing range and string as far as possible because
2687 // this may run a lot, i.e., HTMLEditor creates ReplaceTextTransaction
2688 // a lot for normalizing white-spaces. Then, each transaction shouldn't
2689 // have all white-spaces every time because once it's normalized, we
2690 // don't need to normalize all of the sequence again, but currently
2693 // Finally, extend the range.
2694 if (startToNormalize
.IsSet()) {
2695 aStartToDelete
= startToNormalize
;
2697 if (endToNormalize
.IsSet()) {
2698 aEndToDelete
= endToNormalize
;
2702 Result
<EditorDOMPoint
, nsresult
>
2703 HTMLEditor::DeleteTextAndNormalizeSurroundingWhiteSpaces(
2704 const EditorDOMPointInText
& aStartToDelete
,
2705 const EditorDOMPointInText
& aEndToDelete
,
2706 TreatEmptyTextNodes aTreatEmptyTextNodes
,
2707 DeleteDirection aDeleteDirection
) {
2708 MOZ_ASSERT(aStartToDelete
.IsSetAndValid());
2709 MOZ_ASSERT(aEndToDelete
.IsSetAndValid());
2710 MOZ_ASSERT(aStartToDelete
.EqualsOrIsBefore(aEndToDelete
));
2712 // Use nsString for these replacing string because we should avoid to copy
2713 // the buffer from auto storange to ReplaceTextTransaction.
2714 nsString normalizedWhiteSpacesInFirstNode
, normalizedWhiteSpacesInLastNode
;
2716 // First, check whether we need to normalize white-spaces after deleting
2718 EditorDOMPointInText
startToDelete(aStartToDelete
);
2719 EditorDOMPointInText
endToDelete(aEndToDelete
);
2720 ExtendRangeToDeleteWithNormalizingWhiteSpaces(
2721 startToDelete
, endToDelete
, normalizedWhiteSpacesInFirstNode
,
2722 normalizedWhiteSpacesInLastNode
);
2724 // If extended range is still collapsed, i.e., the caller just wants to
2725 // normalize white-space sequence, but there is no white-spaces which need to
2726 // be replaced, we need to do nothing here.
2727 if (startToDelete
== endToDelete
) {
2728 return aStartToDelete
.To
<EditorDOMPoint
>();
2731 // Note that the container text node of startToDelete may be removed from
2732 // the tree if it becomes empty. Therefore, we need to track the point.
2733 EditorDOMPoint newCaretPosition
;
2734 if (aStartToDelete
.ContainerAsText() == aEndToDelete
.ContainerAsText()) {
2735 newCaretPosition
= aEndToDelete
.To
<EditorDOMPoint
>();
2736 } else if (aDeleteDirection
== DeleteDirection::Forward
) {
2737 newCaretPosition
.SetToEndOf(aStartToDelete
.ContainerAsText());
2739 newCaretPosition
.Set(aEndToDelete
.ContainerAsText(), 0u);
2742 // Then, modify the text nodes in the range.
2744 AutoTrackDOMPoint
trackingNewCaretPosition(RangeUpdaterRef(),
2746 // Use ReplaceTextTransaction if we need to normalize white-spaces in
2747 // the first text node.
2748 if (!normalizedWhiteSpacesInFirstNode
.IsEmpty()) {
2749 EditorDOMPoint
trackingEndToDelete(endToDelete
.ContainerAsText(),
2750 endToDelete
.Offset());
2752 AutoTrackDOMPoint
trackEndToDelete(RangeUpdaterRef(),
2753 &trackingEndToDelete
);
2754 uint32_t lengthToReplaceInFirstTextNode
=
2755 startToDelete
.ContainerAsText() ==
2756 trackingEndToDelete
.ContainerAsText()
2757 ? trackingEndToDelete
.Offset() - startToDelete
.Offset()
2758 : startToDelete
.ContainerAsText()->TextLength() -
2759 startToDelete
.Offset();
2760 nsresult rv
= ReplaceTextWithTransaction(
2761 MOZ_KnownLive(*startToDelete
.ContainerAsText()),
2762 startToDelete
.Offset(), lengthToReplaceInFirstTextNode
,
2763 normalizedWhiteSpacesInFirstNode
);
2764 if (NS_FAILED(rv
)) {
2765 NS_WARNING("HTMLEditor::ReplaceTextWithTransaction() failed");
2768 if (startToDelete
.ContainerAsText() ==
2769 trackingEndToDelete
.ContainerAsText()) {
2770 MOZ_ASSERT(normalizedWhiteSpacesInLastNode
.IsEmpty());
2771 break; // There is no more text which we need to delete.
2774 if (MayHaveMutationEventListeners(
2775 NS_EVENT_BITS_MUTATION_CHARACTERDATAMODIFIED
) &&
2776 (NS_WARN_IF(!trackingEndToDelete
.IsSetAndValid()) ||
2777 NS_WARN_IF(!trackingEndToDelete
.IsInTextNode()))) {
2778 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
);
2780 MOZ_ASSERT(trackingEndToDelete
.IsInTextNode());
2781 endToDelete
.Set(trackingEndToDelete
.ContainerAsText(),
2782 trackingEndToDelete
.Offset());
2783 // If the remaining range was modified by mutation event listener,
2784 // we should stop handling the deletion.
2786 EditorDOMPointInText::AtEndOf(*startToDelete
.ContainerAsText());
2787 if (MayHaveMutationEventListeners(
2788 NS_EVENT_BITS_MUTATION_CHARACTERDATAMODIFIED
) &&
2789 NS_WARN_IF(!startToDelete
.IsBefore(endToDelete
))) {
2790 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
);
2793 // Delete ASCII whiteSpaces in the range simpley if there are some text
2794 // nodes which we don't need to replace their text.
2795 if (normalizedWhiteSpacesInLastNode
.IsEmpty() ||
2796 startToDelete
.ContainerAsText() != endToDelete
.ContainerAsText()) {
2797 // If we need to replace text in the last text node, we should
2798 // delete text before its previous text node.
2799 EditorDOMPointInText endToDeleteExceptReplaceRange
=
2800 normalizedWhiteSpacesInLastNode
.IsEmpty()
2802 : EditorDOMPointInText(endToDelete
.ContainerAsText(), 0);
2803 if (startToDelete
!= endToDeleteExceptReplaceRange
) {
2804 nsresult rv
= DeleteTextAndTextNodesWithTransaction(
2805 startToDelete
, endToDeleteExceptReplaceRange
, aTreatEmptyTextNodes
);
2806 if (NS_FAILED(rv
)) {
2808 "HTMLEditor::DeleteTextAndTextNodesWithTransaction() failed");
2811 if (normalizedWhiteSpacesInLastNode
.IsEmpty()) {
2812 break; // There is no more text which we need to delete.
2814 if (MayHaveMutationEventListeners(
2815 NS_EVENT_BITS_MUTATION_CHARACTERDATAMODIFIED
|
2816 NS_EVENT_BITS_MUTATION_NODEREMOVED
|
2817 NS_EVENT_BITS_MUTATION_NODEREMOVEDFROMDOCUMENT
|
2818 NS_EVENT_BITS_MUTATION_SUBTREEMODIFIED
) &&
2819 (NS_WARN_IF(!endToDeleteExceptReplaceRange
.IsSetAndValid()) ||
2820 NS_WARN_IF(!endToDelete
.IsSetAndValid()) ||
2821 NS_WARN_IF(endToDelete
.IsStartOfContainer()))) {
2822 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
);
2824 // Then, replace the text in the last text node.
2825 startToDelete
= endToDeleteExceptReplaceRange
;
2829 // Replace ASCII whiteSpaces in the range and following character in the
2831 MOZ_ASSERT(!normalizedWhiteSpacesInLastNode
.IsEmpty());
2832 MOZ_ASSERT(startToDelete
.ContainerAsText() ==
2833 endToDelete
.ContainerAsText());
2834 nsresult rv
= ReplaceTextWithTransaction(
2835 MOZ_KnownLive(*startToDelete
.ContainerAsText()), startToDelete
.Offset(),
2836 endToDelete
.Offset() - startToDelete
.Offset(),
2837 normalizedWhiteSpacesInLastNode
);
2838 if (NS_FAILED(rv
)) {
2839 NS_WARNING("HTMLEditor::ReplaceTextWithTransaction() failed");
2845 if (!newCaretPosition
.IsSetAndValid() ||
2846 !newCaretPosition
.GetContainer()->IsInComposedDoc()) {
2848 "HTMLEditor::DeleteTextAndNormalizeSurroundingWhiteSpaces() got lost "
2849 "the modifying line");
2850 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
);
2853 // Look for leaf node to put caret if we remove some empty inline ancestors
2854 // at new caret position.
2855 if (!newCaretPosition
.IsInTextNode()) {
2856 if (const Element
* editableBlockElementOrInlineEditingHost
=
2857 HTMLEditUtils::GetInclusiveAncestorElement(
2858 *newCaretPosition
.ContainerAsContent(),
2860 ClosestEditableBlockElementOrInlineEditingHost
)) {
2861 Element
* editingHost
= ComputeEditingHost();
2862 // Try to put caret next to immediately after previous editable leaf.
2863 nsIContent
* previousContent
=
2864 HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement(
2865 newCaretPosition
, *editableBlockElementOrInlineEditingHost
,
2866 {LeafNodeType::LeafNodeOrNonEditableNode
}, editingHost
);
2867 if (previousContent
&& !HTMLEditUtils::IsBlockElement(*previousContent
)) {
2869 previousContent
->IsText() ||
2870 HTMLEditUtils::IsContainerNode(*previousContent
)
2871 ? EditorDOMPoint::AtEndOf(*previousContent
)
2872 : EditorDOMPoint::After(*previousContent
);
2874 // But if the point is very first of a block element or immediately after
2875 // a child block, look for next editable leaf instead.
2876 else if (nsIContent
* nextContent
=
2877 HTMLEditUtils::GetNextLeafContentOrNextBlockElement(
2879 *editableBlockElementOrInlineEditingHost
,
2880 {LeafNodeType::LeafNodeOrNonEditableNode
},
2882 newCaretPosition
= nextContent
->IsText() ||
2883 HTMLEditUtils::IsContainerNode(*nextContent
)
2884 ? EditorDOMPoint(nextContent
, 0)
2885 : EditorDOMPoint(nextContent
);
2890 // For compatibility with Blink, we should move caret to end of previous
2891 // text node if it's direct previous sibling of the first text node in the
2893 if (newCaretPosition
.IsStartOfContainer() &&
2894 newCaretPosition
.IsInTextNode() &&
2895 newCaretPosition
.GetContainer()->GetPreviousSibling() &&
2896 newCaretPosition
.GetContainer()->GetPreviousSibling()->IsText()) {
2897 newCaretPosition
.SetToEndOf(
2898 newCaretPosition
.GetContainer()->GetPreviousSibling()->AsText());
2902 AutoTrackDOMPoint
trackingNewCaretPosition(RangeUpdaterRef(),
2904 nsresult rv
= InsertBRElementIfHardLineIsEmptyAndEndsWithBlockBoundary(
2906 if (NS_FAILED(rv
)) {
2909 "InsertBRElementIfHardLineIsEmptyAndEndsWithBlockBoundary() failed");
2913 if (!newCaretPosition
.IsSetAndValid()) {
2914 NS_WARNING("Inserting <br> element caused unexpected DOM tree");
2915 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
);
2917 return newCaretPosition
;
2920 nsresult
HTMLEditor::InsertBRElementIfHardLineIsEmptyAndEndsWithBlockBoundary(
2921 const EditorDOMPoint
& aPointToInsert
) {
2922 MOZ_ASSERT(IsEditActionDataAvailable());
2923 MOZ_ASSERT(aPointToInsert
.IsSet());
2925 if (!aPointToInsert
.GetContainerAsContent()) {
2929 // If container of the point is not in a block, we don't need to put a
2930 // `<br>` element here.
2931 if (!HTMLEditUtils::IsBlockElement(*aPointToInsert
.ContainerAsContent())) {
2935 WSRunScanner
wsRunScanner(ComputeEditingHost(), aPointToInsert
);
2936 // If the point is not start of a hard line, we don't need to put a `<br>`
2938 if (!wsRunScanner
.StartsFromHardLineBreak()) {
2941 // If the point is not end of a hard line or the hard line does not end with
2942 // block boundary, we don't need to put a `<br>` element here.
2943 if (!wsRunScanner
.EndsByBlockBoundary()) {
2947 // If we cannot insert a `<br>` element here, do nothing.
2948 if (!HTMLEditUtils::CanNodeContain(*aPointToInsert
.GetContainer(),
2953 CreateElementResult insertBRElementResult
= InsertBRElement(
2954 WithTransaction::Yes
, aPointToInsert
, nsIEditor::ePrevious
);
2955 if (insertBRElementResult
.isErr()) {
2957 "HTMLEditor::InsertBRElement(WithTransaction::Yes, ePrevious) failed");
2958 return insertBRElementResult
.unwrapErr();
2960 nsresult rv
= insertBRElementResult
.SuggestCaretPointTo(*this, {});
2961 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
2962 "CreateElementResult::SuggestCaretPointTo() failed");
2966 EditActionResult
HTMLEditor::MakeOrChangeListAndListItemAsSubAction(
2967 nsAtom
& aListElementOrListItemElementTagName
, const nsAString
& aBulletType
,
2968 SelectAllOfCurrentList aSelectAllOfCurrentList
) {
2969 MOZ_ASSERT(IsEditActionDataAvailable());
2970 MOZ_ASSERT(&aListElementOrListItemElementTagName
== nsGkAtoms::ul
||
2971 &aListElementOrListItemElementTagName
== nsGkAtoms::ol
||
2972 &aListElementOrListItemElementTagName
== nsGkAtoms::dl
||
2973 &aListElementOrListItemElementTagName
== nsGkAtoms::dd
||
2974 &aListElementOrListItemElementTagName
== nsGkAtoms::dt
);
2976 if (NS_WARN_IF(!mInitSucceeded
)) {
2977 return EditActionIgnored(NS_ERROR_NOT_INITIALIZED
);
2980 EditActionResult result
= CanHandleHTMLEditSubAction();
2981 if (result
.Failed() || result
.Canceled()) {
2982 NS_WARNING_ASSERTION(result
.Succeeded(),
2983 "HTMLEditor::CanHandleHTMLEditSubAction() failed");
2987 if (IsSelectionRangeContainerNotContent()) {
2988 NS_WARNING("Some selection containers are not content node, but ignored");
2989 return EditActionIgnored();
2992 AutoPlaceholderBatch
treatAsOneTransaction(
2993 *this, ScrollSelectionIntoView::Yes
, __FUNCTION__
);
2995 // XXX EditSubAction::eCreateOrChangeDefinitionListItem and
2996 // EditSubAction::eCreateOrChangeList are treated differently in
2997 // HTMLEditor::MaybeSplitElementsAtEveryBRElement(). Only when
2998 // EditSubAction::eCreateOrChangeList, it splits inline nodes.
2999 // Currently, it shouldn't be done when we called for formatting
3000 // `<dd>` or `<dt>` by
3001 // HTMLEditor::MakeDefinitionListItemWithTransaction(). But this
3002 // difference may be a bug. We should investigate this later.
3003 IgnoredErrorResult ignoredError
;
3004 AutoEditSubActionNotifier
startToHandleEditSubAction(
3006 &aListElementOrListItemElementTagName
== nsGkAtoms::dd
||
3007 &aListElementOrListItemElementTagName
== nsGkAtoms::dt
3008 ? EditSubAction::eCreateOrChangeDefinitionListItem
3009 : EditSubAction::eCreateOrChangeList
,
3010 nsIEditor::eNext
, ignoredError
);
3011 if (NS_WARN_IF(ignoredError
.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED
))) {
3012 return EditActionResult(ignoredError
.StealNSResult());
3014 NS_WARNING_ASSERTION(
3015 !ignoredError
.Failed(),
3016 "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
3018 nsresult rv
= EnsureNoPaddingBRElementForEmptyEditor();
3019 if (NS_WARN_IF(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
3020 return EditActionResult(NS_ERROR_EDITOR_DESTROYED
);
3022 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
3023 "EditorBase::EnsureNoPaddingBRElementForEmptyEditor() "
3024 "failed, but ignored");
3026 if (NS_SUCCEEDED(rv
) && SelectionRef().IsCollapsed()) {
3027 nsresult rv
= EnsureCaretNotAfterInvisibleBRElement();
3028 if (NS_WARN_IF(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
3029 return EditActionResult(NS_ERROR_EDITOR_DESTROYED
);
3031 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
3032 "HTMLEditor::EnsureCaretNotAfterInvisibleBRElement() "
3033 "failed, but ignored");
3034 if (NS_SUCCEEDED(rv
)) {
3035 nsresult rv
= PrepareInlineStylesForCaret();
3036 if (NS_WARN_IF(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
3037 return EditActionResult(NS_ERROR_EDITOR_DESTROYED
);
3039 NS_WARNING_ASSERTION(
3041 "HTMLEditor::PrepareInlineStylesForCaret() failed, but ignored");
3045 nsAtom
* listTagName
= nullptr;
3046 nsAtom
* listItemTagName
= nullptr;
3047 if (&aListElementOrListItemElementTagName
== nsGkAtoms::ul
||
3048 &aListElementOrListItemElementTagName
== nsGkAtoms::ol
) {
3049 listTagName
= &aListElementOrListItemElementTagName
;
3050 listItemTagName
= nsGkAtoms::li
;
3051 } else if (&aListElementOrListItemElementTagName
== nsGkAtoms::dl
) {
3052 listTagName
= &aListElementOrListItemElementTagName
;
3053 listItemTagName
= nsGkAtoms::dd
;
3054 } else if (&aListElementOrListItemElementTagName
== nsGkAtoms::dd
||
3055 &aListElementOrListItemElementTagName
== nsGkAtoms::dt
) {
3056 listTagName
= nsGkAtoms::dl
;
3057 listItemTagName
= &aListElementOrListItemElementTagName
;
3060 "aListElementOrListItemElementTagName was neither list element name "
3062 "definition listitem element name");
3063 return EditActionResult(NS_ERROR_INVALID_ARG
);
3066 // Expands selection range to include the immediate block parent, and then
3067 // further expands to include any ancestors whose children are all in the
3069 if (!SelectionRef().IsCollapsed()) {
3070 nsresult rv
= MaybeExtendSelectionToHardLineEdgesForBlockEditAction();
3071 if (NS_FAILED(rv
)) {
3073 "HTMLEditor::MaybeExtendSelectionToHardLineEdgesForBlockEditAction() "
3075 return EditActionResult(rv
);
3079 // ChangeSelectedHardLinesToList() creates AutoSelectionRestorer.
3080 // Therefore, even if it returns NS_OK, editor might have been destroyed
3081 // at restoring Selection.
3082 result
= ChangeSelectedHardLinesToList(MOZ_KnownLive(*listTagName
),
3083 MOZ_KnownLive(*listItemTagName
),
3084 aBulletType
, aSelectAllOfCurrentList
);
3085 if (NS_WARN_IF(Destroyed())) {
3086 return EditActionResult(NS_ERROR_EDITOR_DESTROYED
);
3088 NS_WARNING_ASSERTION(result
.Succeeded(),
3089 "HTMLEditor::ChangeSelectedHardLinesToList() failed");
3093 EditActionResult
HTMLEditor::ChangeSelectedHardLinesToList(
3094 nsAtom
& aListElementTagName
, nsAtom
& aListItemElementTagName
,
3095 const nsAString
& aBulletType
,
3096 SelectAllOfCurrentList aSelectAllOfCurrentList
) {
3097 MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable());
3098 MOZ_ASSERT(!IsSelectionRangeContainerNotContent());
3100 RefPtr
<Element
> editingHost
= ComputeEditingHost();
3101 if (MOZ_UNLIKELY(NS_WARN_IF(!editingHost
))) {
3102 return EditActionResult(NS_ERROR_FAILURE
);
3105 AutoSelectionRestorer
restoreSelectionLater(*this);
3107 AutoTArray
<OwningNonNull
<nsIContent
>, 64> arrayOfContents
;
3108 Element
* parentListElement
=
3109 aSelectAllOfCurrentList
== SelectAllOfCurrentList::Yes
3110 ? GetParentListElementAtSelection()
3112 if (parentListElement
) {
3113 arrayOfContents
.AppendElement(
3114 OwningNonNull
<nsIContent
>(*parentListElement
));
3116 AutoTransactionsConserveSelection
dontChangeMySelection(*this);
3118 SplitInlinesAndCollectEditTargetNodesInExtendedSelectionRanges(
3119 arrayOfContents
, EditSubAction::eCreateOrChangeList
,
3120 CollectNonEditableNodes::No
);
3121 if (NS_FAILED(rv
)) {
3124 "SplitInlinesAndCollectEditTargetNodesInExtendedSelectionRanges("
3125 "eCreateOrChangeList, CollectNonEditableNodes::No) failed");
3126 return EditActionResult(rv
);
3130 // check if all our nodes are <br>s, or empty inlines
3131 bool bOnlyBreaks
= true;
3132 for (auto& content
: arrayOfContents
) {
3133 // if content is not a Break or empty inline, we're done
3134 if (!content
->IsHTMLElement(nsGkAtoms::br
) &&
3135 !HTMLEditUtils::IsEmptyInlineContent(content
)) {
3136 bOnlyBreaks
= false;
3141 // if no nodes, we make empty list. Ditto if the user tried to make a list
3142 // of some # of breaks.
3143 if (arrayOfContents
.IsEmpty() || bOnlyBreaks
) {
3144 // if only breaks, delete them
3146 for (auto& content
: arrayOfContents
) {
3147 // MOZ_KnownLive because 'arrayOfContents' is guaranteed to
3149 nsresult rv
= DeleteNodeWithTransaction(MOZ_KnownLive(*content
));
3150 if (NS_FAILED(rv
)) {
3151 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
3152 return EditActionResult(rv
);
3157 const nsRange
* firstRange
= SelectionRef().GetRangeAt(0);
3158 if (NS_WARN_IF(!firstRange
)) {
3159 return EditActionResult(NS_ERROR_FAILURE
);
3162 EditorDOMPoint
atStartOfSelection(firstRange
->StartRef());
3163 if (NS_WARN_IF(!atStartOfSelection
.IsSet())) {
3164 return EditActionResult(NS_ERROR_FAILURE
);
3167 // Make sure we can put a list here.
3168 if (!HTMLEditUtils::CanNodeContain(*atStartOfSelection
.GetContainer(),
3169 aListElementTagName
)) {
3170 return EditActionCanceled();
3173 RefPtr
<Element
> newListItemElement
;
3174 CreateElementResult createNewListElementResult
=
3175 InsertElementWithSplittingAncestorsWithTransaction(
3176 aListElementTagName
, atStartOfSelection
,
3177 BRElementNextToSplitPoint::Keep
, *editingHost
,
3178 // MOZ_CAN_RUN_SCRIPT_BOUNDARY due to bug 1758868
3179 [&newListItemElement
, &aListItemElementTagName
](
3180 HTMLEditor
& aHTMLEditor
, Element
& aListElement
,
3181 const EditorDOMPoint
& aPointToInsert
)
3182 MOZ_CAN_RUN_SCRIPT_BOUNDARY
{
3183 const auto withTransaction
= aListElement
.IsInComposedDoc()
3184 ? WithTransaction::Yes
3185 : WithTransaction::No
;
3186 CreateElementResult createNewListItemElementResult
=
3187 aHTMLEditor
.CreateAndInsertElement(
3188 withTransaction
, aListItemElementTagName
,
3189 EditorDOMPoint(&aListElement
, 0u));
3190 if (createNewListItemElementResult
.isErr()) {
3193 "HTMLEditor::CreateAndInsertElement(%s) failed",
3194 ToString(withTransaction
).c_str())
3196 return createNewListItemElementResult
.unwrapErr();
3198 // There is AutoSelectionRestorer in this method so that it'll
3199 // be restored or updated with making it abort. Therefore,
3200 // we don't need to update selection here.
3201 // XXX I'd like to check restoreSelectionLater here, but it
3202 // requires ifdefs to avoid bustage of opt builds caused
3203 // by unused warning...
3204 createNewListItemElementResult
.IgnoreCaretPointSuggestion();
3205 newListItemElement
=
3206 createNewListItemElementResult
.UnwrapNewNode();
3207 MOZ_ASSERT(newListItemElement
);
3210 if (createNewListElementResult
.isErr()) {
3213 "HTMLEditor::InsertElementWithSplittingAncestorsWithTransaction("
3215 nsAtomCString(&aListElementTagName
).get())
3217 return EditActionResult(createNewListElementResult
.unwrapErr());
3219 MOZ_ASSERT(createNewListElementResult
.GetNewNode());
3221 // remember our new block for postprocessing
3222 TopLevelEditSubActionDataRef().mNewBlockElement
= newListItemElement
;
3223 // Put selection in new list item and don't restore the Selection.
3224 createNewListElementResult
.IgnoreCaretPointSuggestion();
3225 restoreSelectionLater
.Abort();
3226 nsresult rv
= CollapseSelectionToStartOf(*newListItemElement
);
3227 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
3228 "EditorBase::CollapseSelectionToStartOf() failed");
3229 return EditActionResult(rv
);
3232 // if there is only one node in the array, and it is a list, div, or
3233 // blockquote, then look inside of it until we find inner list or content.
3234 if (arrayOfContents
.Length() == 1) {
3235 if (Element
* deepestDivBlockquoteOrListElement
=
3236 HTMLEditUtils::GetInclusiveDeepestFirstChildWhichHasOneChild(
3237 arrayOfContents
[0], {WalkTreeOption::IgnoreNonEditableNode
},
3238 nsGkAtoms::div
, nsGkAtoms::blockquote
, nsGkAtoms::ul
,
3239 nsGkAtoms::ol
, nsGkAtoms::dl
)) {
3240 if (deepestDivBlockquoteOrListElement
->IsAnyOfHTMLElements(
3241 nsGkAtoms::div
, nsGkAtoms::blockquote
)) {
3242 arrayOfContents
.Clear();
3243 CollectChildren(*deepestDivBlockquoteOrListElement
, arrayOfContents
, 0,
3244 CollectListChildren::No
, CollectTableChildren::No
,
3245 CollectNonEditableNodes::Yes
);
3247 arrayOfContents
.ReplaceElementAt(
3248 0, OwningNonNull
<nsIContent
>(*deepestDivBlockquoteOrListElement
));
3253 // Ok, now go through all the nodes and put then in the list,
3254 // or whatever is approriate. Wohoo!
3256 uint32_t countOfCollectedContents
= arrayOfContents
.Length();
3257 RefPtr
<Element
> curList
, prevListItem
;
3259 for (uint32_t i
= 0; i
< countOfCollectedContents
; i
++) {
3260 // here's where we actually figure out what to do
3261 OwningNonNull
<nsIContent
> content
= arrayOfContents
[i
];
3263 // make sure we don't assemble content that is in different table cells
3264 // into the same list. respect table cell boundaries when listifying.
3266 HTMLEditUtils::GetInclusiveAncestorAnyTableElement(*curList
) !=
3267 HTMLEditUtils::GetInclusiveAncestorAnyTableElement(content
)) {
3271 // If current node is a `<br>` element, delete it and forget previous
3272 // list item element.
3273 // If current node is an empty inline node, just delete it.
3274 if (EditorUtils::IsEditableContent(content
, EditorType::HTML
) &&
3275 (content
->IsHTMLElement(nsGkAtoms::br
) ||
3276 HTMLEditUtils::IsEmptyInlineContent(content
))) {
3277 nsresult rv
= DeleteNodeWithTransaction(*content
);
3278 if (NS_FAILED(rv
)) {
3279 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
3280 return EditActionResult(rv
);
3282 if (content
->IsHTMLElement(nsGkAtoms::br
)) {
3283 prevListItem
= nullptr;
3288 if (HTMLEditUtils::IsAnyListElement(content
)) {
3289 // If we met a list element and current list element is not a descendant
3290 // of the list, append current node to end of the current list element.
3291 // Then, wrap it with list item element and delete the old container.
3292 if (curList
&& !EditorUtils::IsDescendantOf(*content
, *curList
)) {
3293 const MoveNodeResult moveNodeResult
=
3294 MoveNodeToEndWithTransaction(*content
, *curList
);
3295 if (moveNodeResult
.isErr()) {
3296 NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed");
3297 return EditActionResult(moveNodeResult
.unwrapErr());
3299 nsresult rv
= moveNodeResult
.SuggestCaretPointTo(
3300 *this, {SuggestCaret::OnlyIfHasSuggestion
,
3301 SuggestCaret::OnlyIfTransactionsAllowedToDoIt
,
3302 SuggestCaret::AndIgnoreTrivialError
});
3303 if (NS_FAILED(rv
)) {
3304 NS_WARNING("MoveNodeResult::SuggestCaretPointTo() failed");
3305 return EditActionResult(rv
);
3307 NS_WARNING_ASSERTION(
3308 rv
!= NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR
,
3309 "MoveNodeResult::SuggestCaretPointTo() failed, but ignored");
3310 CreateElementResult convertListTypeResult
=
3311 ChangeListElementType(MOZ_KnownLive(*content
->AsElement()),
3312 aListElementTagName
, aListItemElementTagName
);
3313 if (convertListTypeResult
.isErr()) {
3314 NS_WARNING("HTMLEditor::ChangeListElementType() failed");
3315 return EditActionResult(convertListTypeResult
.unwrapErr());
3317 const Result
<EditorDOMPoint
, nsresult
> unwrapNewListElementResult
=
3318 RemoveBlockContainerWithTransaction(
3319 MOZ_KnownLive(*convertListTypeResult
.GetNewNode()));
3320 if (MOZ_UNLIKELY(unwrapNewListElementResult
.isErr())) {
3322 "HTMLEditor::RemoveBlockContainerWithTransaction() failed");
3323 return EditActionResult(unwrapNewListElementResult
.inspectErr());
3325 const EditorDOMPoint
& pointToPutCaret
=
3326 unwrapNewListElementResult
.inspect();
3327 if (AllowsTransactionsToChangeSelection() && pointToPutCaret
.IsSet()) {
3328 nsresult rv
= CollapseSelectionTo(pointToPutCaret
);
3329 if (NS_FAILED(rv
)) {
3330 NS_WARNING("EditorBase::CollapseSelectionTo() failed");
3331 return EditActionResult(rv
);
3334 prevListItem
= nullptr;
3338 // If current list element is in found list element or we've not met a
3339 // list element, convert current list element to proper type.
3340 CreateElementResult convertListTypeResult
=
3341 ChangeListElementType(MOZ_KnownLive(*content
->AsElement()),
3342 aListElementTagName
, aListItemElementTagName
);
3343 if (convertListTypeResult
.isErr()) {
3344 NS_WARNING("HTMLEditor::ChangeListElementType() failed");
3345 return EditActionResult(convertListTypeResult
.unwrapErr());
3347 curList
= convertListTypeResult
.UnwrapNewNode();
3348 prevListItem
= nullptr;
3352 EditorDOMPoint
atContent(content
);
3353 if (NS_WARN_IF(!atContent
.IsSet())) {
3354 return EditActionResult(NS_ERROR_FAILURE
);
3356 MOZ_ASSERT(atContent
.IsSetAndValid());
3357 if (HTMLEditUtils::IsListItem(content
)) {
3358 // If current list item element is not in proper list element, we need
3359 // to conver the list element.
3360 if (!atContent
.IsContainerHTMLElement(&aListElementTagName
)) {
3361 // If we've not met a list element or current node is not in current
3362 // list element, insert a list element at current node and set
3363 // current list element to the new one.
3364 if (!curList
|| EditorUtils::IsDescendantOf(*content
, *curList
)) {
3365 if (NS_WARN_IF(!atContent
.GetContainerAsContent())) {
3366 return EditActionResult(NS_ERROR_FAILURE
);
3368 const SplitNodeResult splitListItemParentResult
=
3369 SplitNodeWithTransaction(atContent
);
3370 if (splitListItemParentResult
.isErr()) {
3371 NS_WARNING("HTMLEditor::SplitNodeWithTransaction() failed");
3372 return EditActionResult(splitListItemParentResult
.unwrapErr());
3374 MOZ_ASSERT(splitListItemParentResult
.DidSplit());
3375 // We'll update selection after creating new list element below.
3376 // Therefore, we don't need to handle selection now.
3377 splitListItemParentResult
.IgnoreCaretPointSuggestion();
3378 CreateElementResult createNewListElementResult
=
3379 CreateAndInsertElement(
3380 WithTransaction::Yes
, aListElementTagName
,
3381 splitListItemParentResult
.AtNextContent
<EditorDOMPoint
>());
3382 if (createNewListElementResult
.isErr()) {
3384 "HTMLEditor::CreateAndInsertElement(WithTransaction::Yes) "
3386 return EditActionResult(createNewListElementResult
.unwrapErr());
3388 nsresult rv
= createNewListElementResult
.SuggestCaretPointTo(
3389 *this, {SuggestCaret::OnlyIfHasSuggestion
,
3390 SuggestCaret::OnlyIfTransactionsAllowedToDoIt
,
3391 SuggestCaret::AndIgnoreTrivialError
});
3392 if (NS_FAILED(rv
)) {
3393 NS_WARNING("CreateElementResult::SuggestCaretPointTo() failed");
3394 return EditActionResult(NS_ERROR_EDITOR_DESTROYED
);
3396 NS_WARNING_ASSERTION(
3397 rv
!= NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR
,
3398 "CreateElementResult::SuggestCaretPointTo() failed, but ignored");
3399 curList
= createNewListElementResult
.UnwrapNewNode();
3400 MOZ_ASSERT(curList
);
3402 // Then, move current node into current list element.
3403 const MoveNodeResult moveNodeResult
=
3404 MoveNodeToEndWithTransaction(*content
, *curList
);
3405 if (moveNodeResult
.isErr()) {
3406 NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed");
3407 return EditActionResult(moveNodeResult
.unwrapErr());
3409 nsresult rv
= moveNodeResult
.SuggestCaretPointTo(
3410 *this, {SuggestCaret::OnlyIfHasSuggestion
,
3411 SuggestCaret::OnlyIfTransactionsAllowedToDoIt
,
3412 SuggestCaret::AndIgnoreTrivialError
});
3413 if (NS_FAILED(rv
)) {
3414 NS_WARNING("MoveNodeResult::SuggestCaretPointTo() failed");
3415 return EditActionResult(rv
);
3417 NS_WARNING_ASSERTION(
3418 rv
!= NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR
,
3419 "MoveNodeResult::SuggestCaretPointTo() failed, but ignored");
3420 // Convert list item type if current node is different list item type.
3421 if (!content
->IsHTMLElement(&aListItemElementTagName
)) {
3422 const CreateElementResult newListItemElementOrError
=
3423 ReplaceContainerWithTransaction(
3424 MOZ_KnownLive(*content
->AsElement()),
3425 aListItemElementTagName
);
3426 if (newListItemElementOrError
.isErr()) {
3427 NS_WARNING("HTMLEditor::ReplaceContainerWithTransaction() failed");
3428 return EditActionResult(newListItemElementOrError
.inspectErr());
3430 nsresult rv
= newListItemElementOrError
.SuggestCaretPointTo(
3431 *this, {SuggestCaret::OnlyIfHasSuggestion
,
3432 SuggestCaret::OnlyIfTransactionsAllowedToDoIt
,
3433 SuggestCaret::AndIgnoreTrivialError
});
3434 if (NS_FAILED(rv
)) {
3435 NS_WARNING("CreateElementResult::SuggestCaretPointTo() failed");
3436 return EditActionResult(rv
);
3438 NS_WARNING_ASSERTION(
3439 rv
!= NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR
,
3440 "CreateElementResult::SuggestCaretPointTo() failed, but ignored");
3443 // If we've not met a list element, set current list element to the
3444 // parent of current list item element.
3446 curList
= atContent
.GetContainerAsElement();
3447 NS_WARNING_ASSERTION(
3448 HTMLEditUtils::IsAnyListElement(curList
),
3449 "Current list item parent is not a list element");
3451 // If current list item element is not a child of current list element,
3452 // move it into current list item.
3453 else if (atContent
.GetContainer() != curList
) {
3454 const MoveNodeResult moveNodeResult
=
3455 MoveNodeToEndWithTransaction(*content
, *curList
);
3456 if (moveNodeResult
.isErr()) {
3457 NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed");
3458 return EditActionResult(moveNodeResult
.unwrapErr());
3460 nsresult rv
= moveNodeResult
.SuggestCaretPointTo(
3461 *this, {SuggestCaret::OnlyIfHasSuggestion
,
3462 SuggestCaret::OnlyIfTransactionsAllowedToDoIt
,
3463 SuggestCaret::AndIgnoreTrivialError
});
3464 if (NS_FAILED(rv
)) {
3465 NS_WARNING("MoveNodeResult::SuggestCaretPointTo() failed");
3466 return EditActionResult(rv
);
3468 NS_WARNING_ASSERTION(
3469 rv
!= NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR
,
3470 "MoveNodeResult::SuggestCaretPointTo() failed, but ignored");
3472 // Then, if current list item element is not proper type for current
3473 // list element, convert list item element to proper element.
3474 if (!content
->IsHTMLElement(&aListItemElementTagName
)) {
3475 const CreateElementResult newListItemElementOrError
=
3476 ReplaceContainerWithTransaction(
3477 MOZ_KnownLive(*content
->AsElement()),
3478 aListItemElementTagName
);
3479 if (newListItemElementOrError
.isErr()) {
3480 NS_WARNING("HTMLEditor::ReplaceContainerWithTransaction() failed");
3481 return EditActionResult(newListItemElementOrError
.inspectErr());
3483 nsresult rv
= newListItemElementOrError
.SuggestCaretPointTo(
3484 *this, {SuggestCaret::OnlyIfHasSuggestion
,
3485 SuggestCaret::OnlyIfTransactionsAllowedToDoIt
,
3486 SuggestCaret::AndIgnoreTrivialError
});
3487 if (NS_FAILED(rv
)) {
3488 NS_WARNING("CreateElementResult::SuggestCaretPointTo() failed");
3489 return EditActionResult(rv
);
3491 NS_WARNING_ASSERTION(
3492 rv
!= NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR
,
3493 "CreateElementResult::SuggestCaretPointTo() failed, but ignored");
3496 Element
* element
= Element::FromNode(content
);
3497 if (NS_WARN_IF(!element
)) {
3498 return EditActionResult(NS_ERROR_FAILURE
);
3500 // If bullet type is specified, set list type attribute.
3501 // XXX Cannot we set type attribute before inserting the list item
3502 // element into the DOM tree?
3503 if (!aBulletType
.IsEmpty()) {
3504 nsresult rv
= SetAttributeWithTransaction(
3505 MOZ_KnownLive(*element
), *nsGkAtoms::type
, aBulletType
);
3506 if (NS_WARN_IF(Destroyed())) {
3507 return EditActionResult(NS_ERROR_EDITOR_DESTROYED
);
3509 if (NS_FAILED(rv
)) {
3511 "EditorBase::SetAttributeWithTransaction(nsGkAtoms::type) "
3513 return EditActionResult(rv
);
3518 // Otherwise, remove list type attribute if there is.
3519 if (!element
->HasAttr(nsGkAtoms::type
)) {
3522 nsresult rv
= RemoveAttributeWithTransaction(MOZ_KnownLive(*element
),
3524 if (NS_WARN_IF(Destroyed())) {
3525 return EditActionResult(NS_ERROR_EDITOR_DESTROYED
);
3527 if (NS_FAILED(rv
)) {
3529 "EditorBase::RemoveAttributeWithTransaction(nsGkAtoms::type) "
3531 return EditActionResult(rv
);
3536 MOZ_ASSERT(!HTMLEditUtils::IsAnyListElement(content
) &&
3537 !HTMLEditUtils::IsListItem(content
));
3539 // If current node is a `<div>` element, replace it in the array with
3541 // XXX I think that this should be done when we collect the nodes above.
3542 // Then, we can change this `for` loop to ranged-for loop.
3543 if (content
->IsHTMLElement(nsGkAtoms::div
)) {
3544 prevListItem
= nullptr;
3545 CollectChildren(*content
, arrayOfContents
, i
+ 1,
3546 CollectListChildren::Yes
, CollectTableChildren::Yes
,
3547 CollectNonEditableNodes::Yes
);
3548 const Result
<EditorDOMPoint
, nsresult
> unwrapDivElementResult
=
3549 RemoveContainerWithTransaction(MOZ_KnownLive(*content
->AsElement()));
3550 if (MOZ_UNLIKELY(unwrapDivElementResult
.isErr())) {
3551 NS_WARNING("HTMLEditor::RemoveContainerWithTransaction() failed");
3552 return EditActionResult(unwrapDivElementResult
.inspectErr());
3554 const EditorDOMPoint
& pointToPutCaret
= unwrapDivElementResult
.inspect();
3555 if (AllowsTransactionsToChangeSelection() && pointToPutCaret
.IsSet()) {
3556 nsresult rv
= CollapseSelectionTo(pointToPutCaret
);
3557 if (NS_FAILED(rv
)) {
3558 NS_WARNING("EditorBase::CollapseSelectionTo() failed");
3559 return EditActionResult(rv
);
3562 // Extend the loop length to handle all children collected here.
3563 countOfCollectedContents
= arrayOfContents
.Length();
3567 // If we've not met a list element, create a list element and make it
3568 // current list element.
3570 prevListItem
= nullptr;
3571 CreateElementResult createNewListElementResult
=
3572 InsertElementWithSplittingAncestorsWithTransaction(
3573 aListElementTagName
, atContent
, BRElementNextToSplitPoint::Keep
,
3575 if (createNewListElementResult
.isErr()) {
3579 "InsertElementWithSplittingAncestorsWithTransaction(%s) failed",
3580 nsAtomCString(&aListElementTagName
).get())
3582 return EditActionResult(createNewListElementResult
.unwrapErr());
3584 // We'll restore selection so that we don't need to update selection now.
3585 createNewListElementResult
.IgnoreCaretPointSuggestion();
3586 MOZ_ASSERT(restoreSelectionLater
.MaybeRestoreSelectionLater());
3588 MOZ_ASSERT(createNewListElementResult
.GetNewNode());
3589 curList
= createNewListElementResult
.UnwrapNewNode();
3590 // Set new block element of top level edit sub-action to the new list
3591 // element for setting selection into it.
3592 // XXX This must be wrong. If we're handling nested edit action,
3593 // we shouldn't overwrite the new block element.
3594 TopLevelEditSubActionDataRef().mNewBlockElement
= curList
;
3596 // atContent is now referring the right node with mOffset but
3597 // referring the left node with mRef. So, invalidate it now.
3601 // If we're currently handling contents of a list item and current node
3602 // is not a block element, move current node into the list item.
3603 if (HTMLEditUtils::IsInlineElement(content
) && prevListItem
) {
3604 const MoveNodeResult moveInlineElementResult
=
3605 MoveNodeToEndWithTransaction(*content
, *prevListItem
);
3606 if (moveInlineElementResult
.isErr()) {
3607 NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed");
3608 return EditActionResult(moveInlineElementResult
.unwrapErr());
3610 nsresult rv
= moveInlineElementResult
.SuggestCaretPointTo(
3611 *this, {SuggestCaret::OnlyIfHasSuggestion
,
3612 SuggestCaret::OnlyIfTransactionsAllowedToDoIt
,
3613 SuggestCaret::AndIgnoreTrivialError
});
3614 if (NS_FAILED(rv
)) {
3615 NS_WARNING("MoveNodeResult::SuggestCaretPointTo() failed");
3616 return EditActionResult(rv
);
3618 NS_WARNING_ASSERTION(
3619 rv
!= NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR
,
3620 "MoveNodeResult::SuggestCaretPointTo() failed, but ignored");
3624 // If current node is a paragraph, that means that it does not contain
3625 // block children so that we can just replace it with new list item
3626 // element and move it into current list element.
3627 // XXX This is too rough handling. If web apps modifies DOM tree directly,
3628 // any elements can have block elements as children.
3629 if (content
->IsHTMLElement(nsGkAtoms::p
)) {
3630 CreateElementResult newListItemElementOrError
=
3631 ReplaceContainerWithTransaction(MOZ_KnownLive(*content
->AsElement()),
3632 aListItemElementTagName
);
3633 if (newListItemElementOrError
.isErr()) {
3634 NS_WARNING("HTMLEditor::ReplaceContainerWithTransaction() failed");
3635 return EditActionResult(newListItemElementOrError
.inspectErr());
3637 // Collapse selection after moving the list item element.
3638 newListItemElementOrError
.IgnoreCaretPointSuggestion();
3639 const OwningNonNull
<Element
> newListItemElement
=
3640 newListItemElementOrError
.UnwrapNewNode();
3641 prevListItem
= nullptr;
3642 const MoveNodeResult moveListItemElementResult
=
3643 MoveNodeToEndWithTransaction(newListItemElement
, *curList
);
3644 if (moveListItemElementResult
.isErr()) {
3645 NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed");
3646 return EditActionResult(moveListItemElementResult
.unwrapErr());
3648 nsresult rv
= moveListItemElementResult
.SuggestCaretPointTo(
3649 *this, {SuggestCaret::OnlyIfHasSuggestion
,
3650 SuggestCaret::OnlyIfTransactionsAllowedToDoIt
,
3651 SuggestCaret::AndIgnoreTrivialError
});
3652 if (NS_FAILED(rv
)) {
3653 NS_WARNING("MoveNodeResult::SuggestCaretPointTo() failed");
3654 return EditActionResult(rv
);
3656 NS_WARNING_ASSERTION(
3657 rv
!= NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR
,
3658 "MoveNodeResult::SuggestCaretPointTo() failed, but ignored");
3659 // XXX Why don't we set `type` attribute here??
3663 // If current node is not a paragraph, wrap current node with new list
3664 // item element and move it into current list element.
3665 RefPtr
<Element
> newListItemElement
=
3666 InsertContainerWithTransaction(*content
, aListItemElementTagName
);
3667 if (NS_WARN_IF(Destroyed())) {
3668 return EditActionResult(NS_ERROR_EDITOR_DESTROYED
);
3670 if (!newListItemElement
) {
3671 NS_WARNING("HTMLEditor::InsertContainerWithTransaction() failed");
3672 return EditActionResult(NS_ERROR_FAILURE
);
3674 // If current node is not a block element, new list item should have
3675 // following inline nodes too.
3676 if (HTMLEditUtils::IsInlineElement(content
)) {
3677 prevListItem
= newListItemElement
;
3679 prevListItem
= nullptr;
3681 const MoveNodeResult moveListItemElementResult
=
3682 MoveNodeToEndWithTransaction(*newListItemElement
, *curList
);
3683 if (moveListItemElementResult
.isErr()) {
3684 NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed");
3685 return EditActionResult(moveListItemElementResult
.unwrapErr());
3687 nsresult rv
= moveListItemElementResult
.SuggestCaretPointTo(
3688 *this, {SuggestCaret::OnlyIfHasSuggestion
,
3689 SuggestCaret::OnlyIfTransactionsAllowedToDoIt
,
3690 SuggestCaret::AndIgnoreTrivialError
});
3691 if (NS_FAILED(rv
)) {
3692 NS_WARNING("MoveNodeResult::SuggestCaretPointTo() failed");
3693 return EditActionResult(rv
);
3695 NS_WARNING_ASSERTION(
3696 rv
!= NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR
,
3697 "MoveNodeResult::SuggestCaretPointTo() failed, but ignored");
3698 // XXX Why don't we set `type` attribute here??
3701 return EditActionHandled();
3704 nsresult
HTMLEditor::RemoveListAtSelectionAsSubAction() {
3705 MOZ_ASSERT(IsEditActionDataAvailable());
3707 EditActionResult result
= CanHandleHTMLEditSubAction();
3708 if (result
.Failed() || result
.Canceled()) {
3709 NS_WARNING_ASSERTION(result
.Succeeded(),
3710 "HTMLEditor::CanHandleHTMLEditSubAction() failed");
3714 AutoPlaceholderBatch
treatAsOneTransaction(
3715 *this, ScrollSelectionIntoView::Yes
, __FUNCTION__
);
3716 IgnoredErrorResult ignoredError
;
3717 AutoEditSubActionNotifier
startToHandleEditSubAction(
3718 *this, EditSubAction::eRemoveList
, nsIEditor::eNext
, ignoredError
);
3719 if (NS_WARN_IF(ignoredError
.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED
))) {
3720 return ignoredError
.StealNSResult();
3722 NS_WARNING_ASSERTION(
3723 !ignoredError
.Failed(),
3724 "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
3726 if (!SelectionRef().IsCollapsed()) {
3727 nsresult rv
= MaybeExtendSelectionToHardLineEdgesForBlockEditAction();
3728 if (NS_FAILED(rv
)) {
3730 "HTMLEditor::MaybeExtendSelectionToHardLineEdgesForBlockEditAction() "
3736 AutoSelectionRestorer
restoreSelectionLater(*this);
3738 AutoTArray
<OwningNonNull
<nsIContent
>, 64> arrayOfContents
;
3740 AutoTransactionsConserveSelection
dontChangeMySelection(*this);
3742 SplitInlinesAndCollectEditTargetNodesInExtendedSelectionRanges(
3743 arrayOfContents
, EditSubAction::eCreateOrChangeList
,
3744 CollectNonEditableNodes::No
);
3745 if (NS_FAILED(rv
)) {
3748 "SplitInlinesAndCollectEditTargetNodesInExtendedSelectionRanges("
3749 "eCreateOrChangeList, CollectNonEditableNodes::No) failed");
3754 // Remove all non-editable nodes. Leave them be.
3755 // XXX SplitInlinesAndCollectEditTargetNodesInExtendedSelectionRanges()
3756 // should return only editable contents when it's called with
3757 // CollectNonEditableNodes::No.
3758 for (int32_t i
= arrayOfContents
.Length() - 1; i
>= 0; i
--) {
3759 OwningNonNull
<nsIContent
>& content
= arrayOfContents
[i
];
3760 if (!EditorUtils::IsEditableContent(content
, EditorType::HTML
)) {
3761 arrayOfContents
.RemoveElementAt(i
);
3765 // Only act on lists or list items in the array
3766 for (auto& content
: arrayOfContents
) {
3767 // here's where we actually figure out what to do
3768 if (HTMLEditUtils::IsListItem(content
)) {
3769 // unlist this listitem
3770 nsresult rv
= LiftUpListItemElement(MOZ_KnownLive(*content
->AsElement()),
3771 LiftUpFromAllParentListElements::Yes
);
3772 if (NS_FAILED(rv
)) {
3774 "HTMLEditor::LiftUpListItemElement(LiftUpFromAllParentListElements:"
3780 if (HTMLEditUtils::IsAnyListElement(content
)) {
3781 // node is a list, move list items out
3783 DestroyListStructureRecursively(MOZ_KnownLive(*content
->AsElement()));
3784 if (NS_FAILED(rv
)) {
3785 NS_WARNING("HTMLEditor::DestroyListStructureRecursively() failed");
3794 nsresult
HTMLEditor::FormatBlockContainerWithTransaction(nsAtom
& blockType
) {
3795 MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable());
3797 RefPtr
<Element
> editingHost
= ComputeEditingHost();
3798 if (MOZ_UNLIKELY(NS_WARN_IF(!editingHost
))) {
3799 return NS_ERROR_FAILURE
;
3802 if (!SelectionRef().IsCollapsed()) {
3803 nsresult rv
= MaybeExtendSelectionToHardLineEdgesForBlockEditAction();
3804 if (NS_FAILED(rv
)) {
3806 "HTMLEditor::MaybeExtendSelectionToHardLineEdgesForBlockEditAction() "
3812 AutoSelectionRestorer
restoreSelectionLater(*this);
3813 AutoTransactionsConserveSelection
dontChangeMySelection(*this);
3815 AutoTArray
<OwningNonNull
<nsIContent
>, 64> arrayOfContents
;
3816 nsresult rv
= SplitInlinesAndCollectEditTargetNodesInExtendedSelectionRanges(
3817 arrayOfContents
, EditSubAction::eCreateOrRemoveBlock
,
3818 CollectNonEditableNodes::Yes
);
3819 if (NS_FAILED(rv
)) {
3822 "SplitInlinesAndCollectEditTargetNodesInExtendedSelectionRanges("
3823 "eCreateOrRemoveBlock, CollectNonEditableNodes::Yes) failed");
3827 // If there is no visible and editable nodes in the edit targets, make an
3829 // XXX Isn't this odd if there are only non-editable visible nodes?
3830 if (HTMLEditUtils::IsEmptyOneHardLine(arrayOfContents
)) {
3831 const nsRange
* firstRange
= SelectionRef().GetRangeAt(0);
3832 if (NS_WARN_IF(!firstRange
)) {
3833 return NS_ERROR_FAILURE
;
3836 EditorDOMPoint
pointToInsertBlock(firstRange
->StartRef());
3837 if (&blockType
== nsGkAtoms::normal
|| &blockType
== nsGkAtoms::_empty
) {
3838 if (!pointToInsertBlock
.IsInContentNode()) {
3840 "HTMLEditor::FormatBlockContainerWithTransaction() couldn't find "
3841 "block parent because container of the point is not content");
3842 return NS_ERROR_FAILURE
;
3844 // We are removing blocks (going to "body text")
3845 const RefPtr
<Element
> editableBlockElement
=
3846 HTMLEditUtils::GetInclusiveAncestorElement(
3847 *pointToInsertBlock
.ContainerAsContent(),
3848 HTMLEditUtils::ClosestEditableBlockElement
);
3849 if (!editableBlockElement
) {
3851 "HTMLEditor::FormatBlockContainerWithTransaction() couldn't find "
3853 return NS_ERROR_FAILURE
;
3855 if (!HTMLEditUtils::IsFormatNode(editableBlockElement
)) {
3859 // If the first editable node after selection is a br, consume it.
3860 // Otherwise it gets pushed into a following block after the split,
3861 // which is visually bad.
3862 if (nsCOMPtr
<nsIContent
> brContent
= HTMLEditUtils::GetNextContent(
3863 pointToInsertBlock
, {WalkTreeOption::IgnoreNonEditableNode
},
3865 if (brContent
&& brContent
->IsHTMLElement(nsGkAtoms::br
)) {
3866 AutoEditorDOMPointChildInvalidator
lockOffset(pointToInsertBlock
);
3867 nsresult rv
= DeleteNodeWithTransaction(*brContent
);
3868 if (NS_FAILED(rv
)) {
3869 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
3875 const SplitNodeResult splitNodeResult
= SplitNodeDeepWithTransaction(
3876 *editableBlockElement
, pointToInsertBlock
,
3877 SplitAtEdges::eDoNotCreateEmptyContainer
);
3878 if (splitNodeResult
.isErr()) {
3879 NS_WARNING("HTMLEditor::SplitNodeDeepWithTransaction() failed");
3880 return splitNodeResult
.unwrapErr();
3882 // Put a <br> element at the split point
3883 const CreateElementResult insertBRElementResult
= InsertBRElement(
3884 WithTransaction::Yes
, splitNodeResult
.AtSplitPoint
<EditorDOMPoint
>());
3885 if (insertBRElementResult
.isErr()) {
3886 NS_WARNING("HTMLEditor::InsertBRElement(WithTransaction::Yes) failed");
3887 return insertBRElementResult
.unwrapErr();
3889 MOZ_ASSERT(insertBRElementResult
.GetNewNode());
3890 // Don't restore the selection
3891 restoreSelectionLater
.Abort();
3892 // Put selection at the split point
3893 splitNodeResult
.IgnoreCaretPointSuggestion();
3894 nsresult rv
= CollapseSelectionTo(
3895 EditorRawDOMPoint(insertBRElementResult
.GetNewNode()));
3896 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
3897 "EditorBase::CollapseSelectionTo() failed");
3901 // We are making a block. Consume a br, if needed.
3902 if (nsCOMPtr
<nsIContent
> maybeBRContent
= HTMLEditUtils::GetNextContent(
3904 {WalkTreeOption::IgnoreNonEditableNode
,
3905 WalkTreeOption::StopAtBlockBoundary
},
3907 if (maybeBRContent
->IsHTMLElement(nsGkAtoms::br
)) {
3908 AutoEditorDOMPointChildInvalidator
lockOffset(pointToInsertBlock
);
3909 nsresult rv
= DeleteNodeWithTransaction(*maybeBRContent
);
3910 if (NS_FAILED(rv
)) {
3911 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
3914 // We don't need to act on this node any more
3915 arrayOfContents
.RemoveElement(maybeBRContent
);
3918 // Make sure we can put a block here.
3919 CreateElementResult createNewBlockElementResult
=
3920 InsertElementWithSplittingAncestorsWithTransaction(
3921 blockType
, pointToInsertBlock
, BRElementNextToSplitPoint::Keep
,
3923 if (createNewBlockElementResult
.isErr()) {
3926 "HTMLEditor::InsertElementWithSplittingAncestorsWithTransaction("
3928 nsAtomCString(&blockType
).get())
3930 return createNewBlockElementResult
.unwrapErr();
3932 // We'll update selection after deleting the content nodes and nobody refers
3933 // selection until then. Therefore, we don't need to update selection here.
3934 createNewBlockElementResult
.IgnoreCaretPointSuggestion();
3935 MOZ_ASSERT(restoreSelectionLater
.MaybeRestoreSelectionLater());
3937 MOZ_ASSERT(createNewBlockElementResult
.GetNewNode());
3938 // Remember our new block for postprocessing
3939 TopLevelEditSubActionDataRef().mNewBlockElement
=
3940 createNewBlockElementResult
.GetNewNode();
3941 // Delete anything that was in the list of nodes
3942 while (!arrayOfContents
.IsEmpty()) {
3943 OwningNonNull
<nsIContent
>& content
= arrayOfContents
[0];
3944 // MOZ_KnownLive because 'arrayOfContents' is guaranteed to
3946 nsresult rv
= DeleteNodeWithTransaction(MOZ_KnownLive(*content
));
3947 if (NS_FAILED(rv
)) {
3948 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
3951 arrayOfContents
.RemoveElementAt(0);
3953 // Don't restore the selection
3954 restoreSelectionLater
.Abort();
3955 // Put selection in new block
3956 // MOZ_KnownLive(createNewBlockElementResult.GetNewNode()) because it's
3957 // grabbed by createNewBlockElementResult.
3958 rv
= CollapseSelectionToStartOf(
3959 MOZ_KnownLive(*createNewBlockElementResult
.GetNewNode()));
3960 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
3961 "EditorBase::CollapseSelectionToStartOf() failed");
3964 // Okay, now go through all the nodes and make the right kind of blocks, or
3965 // whatever is approriate. Woohoo! Note: blockquote is handled a little
3967 if (&blockType
== nsGkAtoms::blockquote
) {
3968 nsresult rv
= MoveNodesIntoNewBlockquoteElement(arrayOfContents
);
3969 NS_WARNING_ASSERTION(
3971 "HTMLEditor::MoveNodesIntoNewBlockquoteElement() failed");
3974 if (&blockType
== nsGkAtoms::normal
|| &blockType
== nsGkAtoms::_empty
) {
3975 nsresult rv
= RemoveBlockContainerElements(arrayOfContents
);
3976 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
3977 "HTMLEditor::RemoveBlockContainerElements() failed");
3980 rv
= CreateOrChangeBlockContainerElement(arrayOfContents
, blockType
);
3981 NS_WARNING_ASSERTION(
3983 "HTMLEditor::CreateOrChangeBlockContainerElement() failed");
3987 nsresult
HTMLEditor::MaybeInsertPaddingBRElementForEmptyLastLineAtSelection() {
3988 MOZ_ASSERT(IsEditActionDataAvailable());
3989 MOZ_ASSERT(!IsSelectionRangeContainerNotContent());
3991 if (!SelectionRef().IsCollapsed()) {
3995 const nsRange
* firstRange
= SelectionRef().GetRangeAt(0);
3996 if (NS_WARN_IF(!firstRange
)) {
3997 return NS_ERROR_FAILURE
;
3999 const RangeBoundary
& atStartOfSelection
= firstRange
->StartRef();
4000 if (NS_WARN_IF(!atStartOfSelection
.IsSet())) {
4001 return NS_ERROR_FAILURE
;
4003 if (!atStartOfSelection
.Container()->IsElement()) {
4006 OwningNonNull
<Element
> startContainerElement
=
4007 *atStartOfSelection
.Container()->AsElement();
4009 InsertPaddingBRElementForEmptyLastLineIfNeeded(startContainerElement
);
4010 NS_WARNING_ASSERTION(
4012 "HTMLEditor::InsertPaddingBRElementForEmptyLastLineIfNeeded() failed");
4016 EditActionResult
HTMLEditor::IndentAsSubAction() {
4017 MOZ_ASSERT(IsEditActionDataAvailable());
4019 AutoPlaceholderBatch
treatAsOneTransaction(
4020 *this, ScrollSelectionIntoView::Yes
, __FUNCTION__
);
4021 IgnoredErrorResult ignoredError
;
4022 AutoEditSubActionNotifier
startToHandleEditSubAction(
4023 *this, EditSubAction::eIndent
, nsIEditor::eNext
, ignoredError
);
4024 if (NS_WARN_IF(ignoredError
.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED
))) {
4025 return EditActionResult(ignoredError
.StealNSResult());
4027 NS_WARNING_ASSERTION(
4028 !ignoredError
.Failed(),
4029 "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
4031 EditActionResult result
= CanHandleHTMLEditSubAction();
4032 if (result
.Failed() || result
.Canceled()) {
4033 NS_WARNING_ASSERTION(result
.Succeeded(),
4034 "HTMLEditor::CanHandleHTMLEditSubAction() failed");
4038 if (IsSelectionRangeContainerNotContent()) {
4039 NS_WARNING("Some selection containers are not content node, but ignored");
4040 return EditActionIgnored();
4043 result
|= HandleIndentAtSelection();
4044 if (result
.Failed() || result
.Canceled()) {
4045 NS_WARNING_ASSERTION(result
.Succeeded(),
4046 "HTMLEditor::HandleIndentAtSelection() failed");
4050 if (IsSelectionRangeContainerNotContent()) {
4051 NS_WARNING("Mutation event listener might have changed selection");
4052 return EditActionHandled(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
);
4055 nsresult rv
= MaybeInsertPaddingBRElementForEmptyLastLineAtSelection();
4056 NS_WARNING_ASSERTION(
4058 "MaybeInsertPaddingBRElementForEmptyLastLineAtSelection() failed");
4059 return result
.SetResult(rv
);
4062 // Helper for Handle[CSS|HTML]IndentAtSelectionInternal
4063 nsresult
HTMLEditor::IndentListChild(RefPtr
<Element
>* aCurList
,
4064 const EditorDOMPoint
& aCurPoint
,
4065 nsIContent
& aContent
) {
4066 MOZ_ASSERT(HTMLEditUtils::IsAnyListElement(aCurPoint
.GetContainer()),
4067 "unexpected container");
4068 MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable());
4070 RefPtr
<Element
> editingHost
= ComputeEditingHost();
4071 if (MOZ_UNLIKELY(NS_WARN_IF(!editingHost
))) {
4072 return NS_ERROR_FAILURE
;
4075 // some logic for putting list items into nested lists...
4077 // Check for whether we should join a list that follows aContent.
4078 // We do this if the next element is a list, and the list is of the
4079 // same type (li/ol) as aContent was a part it.
4080 if (nsIContent
* nextEditableSibling
= HTMLEditUtils::GetNextSibling(
4081 aContent
, {WalkTreeOption::IgnoreWhiteSpaceOnlyText
,
4082 WalkTreeOption::IgnoreNonEditableNode
})) {
4083 if (HTMLEditUtils::IsAnyListElement(nextEditableSibling
) &&
4084 aCurPoint
.GetContainer()->NodeInfo()->NameAtom() ==
4085 nextEditableSibling
->NodeInfo()->NameAtom() &&
4086 aCurPoint
.GetContainer()->NodeInfo()->NamespaceID() ==
4087 nextEditableSibling
->NodeInfo()->NamespaceID()) {
4088 const MoveNodeResult moveListElementResult
= MoveNodeWithTransaction(
4089 aContent
, EditorDOMPoint(nextEditableSibling
, 0u));
4090 if (moveListElementResult
.isErr()) {
4091 NS_WARNING("HTMLEditor::MoveNodeWithTransaction() failed");
4092 return moveListElementResult
.unwrapErr();
4094 nsresult rv
= moveListElementResult
.SuggestCaretPointTo(
4095 *this, {SuggestCaret::OnlyIfHasSuggestion
,
4096 SuggestCaret::OnlyIfTransactionsAllowedToDoIt
,
4097 SuggestCaret::AndIgnoreTrivialError
});
4098 if (NS_FAILED(rv
)) {
4099 NS_WARNING("MoveNodeResult::SuggestCaretPointTo() failed");
4102 NS_WARNING_ASSERTION(
4103 rv
!= NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR
,
4104 "MoveNodeResult::SuggestCaretPointTo() failed, but ignored");
4109 // Check for whether we should join a list that preceeds aContent.
4110 // We do this if the previous element is a list, and the list is of
4111 // the same type (li/ol) as aContent was a part of.
4112 if (nsCOMPtr
<nsIContent
> previousEditableSibling
=
4113 HTMLEditUtils::GetPreviousSibling(
4114 aContent
, {WalkTreeOption::IgnoreWhiteSpaceOnlyText
,
4115 WalkTreeOption::IgnoreNonEditableNode
})) {
4116 if (HTMLEditUtils::IsAnyListElement(previousEditableSibling
) &&
4117 aCurPoint
.GetContainer()->NodeInfo()->NameAtom() ==
4118 previousEditableSibling
->NodeInfo()->NameAtom() &&
4119 aCurPoint
.GetContainer()->NodeInfo()->NamespaceID() ==
4120 previousEditableSibling
->NodeInfo()->NamespaceID()) {
4121 const MoveNodeResult moveListElementResult
=
4122 MoveNodeToEndWithTransaction(aContent
, *previousEditableSibling
);
4123 if (moveListElementResult
.isErr()) {
4124 NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed");
4125 return moveListElementResult
.unwrapErr();
4127 nsresult rv
= moveListElementResult
.SuggestCaretPointTo(
4128 *this, {SuggestCaret::OnlyIfHasSuggestion
,
4129 SuggestCaret::OnlyIfTransactionsAllowedToDoIt
,
4130 SuggestCaret::AndIgnoreTrivialError
});
4131 if (NS_FAILED(rv
)) {
4132 NS_WARNING("MoveNodeResult::SuggestCaretPointTo() failed");
4135 NS_WARNING_ASSERTION(
4136 rv
!= NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR
,
4137 "MoveNodeResult::SuggestCaretPointTo() failed, but ignored");
4142 // check to see if aCurList is still appropriate. Which it is if
4143 // aContent is still right after it in the same list.
4144 nsIContent
* previousEditableSibling
=
4145 *aCurList
? HTMLEditUtils::GetPreviousSibling(
4146 aContent
, {WalkTreeOption::IgnoreWhiteSpaceOnlyText
,
4147 WalkTreeOption::IgnoreNonEditableNode
})
4150 (previousEditableSibling
&& previousEditableSibling
!= *aCurList
)) {
4151 nsAtom
* containerName
= aCurPoint
.GetContainer()->NodeInfo()->NameAtom();
4152 // Create a new nested list of correct type.
4153 CreateElementResult createNewListElementResult
=
4154 InsertElementWithSplittingAncestorsWithTransaction(
4155 MOZ_KnownLive(*containerName
), aCurPoint
,
4156 BRElementNextToSplitPoint::Keep
, *editingHost
);
4157 if (createNewListElementResult
.isErr()) {
4160 "HTMLEditor::InsertElementWithSplittingAncestorsWithTransaction("
4162 nsAtomCString(containerName
).get())
4164 return createNewListElementResult
.unwrapErr();
4166 nsresult rv
= createNewListElementResult
.SuggestCaretPointTo(
4167 *this, {SuggestCaret::OnlyIfHasSuggestion
,
4168 SuggestCaret::OnlyIfTransactionsAllowedToDoIt
});
4169 if (NS_FAILED(rv
)) {
4170 NS_WARNING("CreateElementResult::SuggestCaretPointTo() failed");
4173 MOZ_ASSERT(createNewListElementResult
.GetNewNode());
4174 // aCurList is now the correct thing to put aContent in
4175 // remember our new block for postprocessing
4176 TopLevelEditSubActionDataRef().mNewBlockElement
=
4177 createNewListElementResult
.GetNewNode();
4178 *aCurList
= createNewListElementResult
.UnwrapNewNode();
4180 // tuck the node into the end of the active list
4181 RefPtr
<nsINode
> container
= *aCurList
;
4182 const MoveNodeResult moveNodeResult
=
4183 MoveNodeToEndWithTransaction(aContent
, *container
);
4184 if (moveNodeResult
.isErr()) {
4185 NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed");
4186 return moveNodeResult
.unwrapErr();
4188 nsresult rv
= moveNodeResult
.SuggestCaretPointTo(
4189 *this, {SuggestCaret::OnlyIfHasSuggestion
,
4190 SuggestCaret::OnlyIfTransactionsAllowedToDoIt
,
4191 SuggestCaret::AndIgnoreTrivialError
});
4192 if (NS_FAILED(rv
)) {
4193 NS_WARNING("MoveNodeResult::SuggestCaretPointTo() failed");
4196 NS_WARNING_ASSERTION(
4197 rv
!= NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR
,
4198 "MoveNodeResult::SuggestCaretPointTo() failed, but ignored");
4202 EditActionResult
HTMLEditor::HandleIndentAtSelection() {
4203 MOZ_ASSERT(IsEditActionDataAvailable());
4204 MOZ_ASSERT(!IsSelectionRangeContainerNotContent());
4206 nsresult rv
= EnsureNoPaddingBRElementForEmptyEditor();
4207 if (NS_WARN_IF(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
4208 return EditActionResult(NS_ERROR_EDITOR_DESTROYED
);
4210 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
4211 "EditorBase::EnsureNoPaddingBRElementForEmptyEditor() "
4212 "failed, but ignored");
4214 if (NS_SUCCEEDED(rv
) && SelectionRef().IsCollapsed()) {
4215 nsresult rv
= EnsureCaretNotAfterInvisibleBRElement();
4216 if (NS_WARN_IF(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
4217 return EditActionResult(NS_ERROR_EDITOR_DESTROYED
);
4219 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
4220 "HTMLEditor::EnsureCaretNotAfterInvisibleBRElement() "
4221 "failed, but ignored");
4222 if (NS_SUCCEEDED(rv
)) {
4223 nsresult rv
= PrepareInlineStylesForCaret();
4224 if (NS_WARN_IF(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
4225 return EditActionResult(NS_ERROR_EDITOR_DESTROYED
);
4227 NS_WARNING_ASSERTION(
4229 "HTMLEditor::PrepareInlineStylesForCaret() failed, but ignored");
4233 if (IsSelectionRangeContainerNotContent()) {
4234 NS_WARNING("Mutation event listener might have changed the selection");
4235 return EditActionHandled(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
);
4238 if (IsCSSEnabled()) {
4239 nsresult rv
= HandleCSSIndentAtSelection();
4240 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
4241 "HTMLEditor::HandleCSSIndentAtSelection() failed");
4242 return EditActionHandled(rv
);
4244 rv
= HandleHTMLIndentAtSelection();
4245 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
4246 "HTMLEditor::HandleHTMLIndent() failed");
4247 return EditActionHandled(rv
);
4250 nsresult
HTMLEditor::HandleCSSIndentAtSelection() {
4251 MOZ_ASSERT(IsEditActionDataAvailable());
4252 MOZ_ASSERT(!IsSelectionRangeContainerNotContent());
4254 if (!SelectionRef().IsCollapsed()) {
4255 nsresult rv
= MaybeExtendSelectionToHardLineEdgesForBlockEditAction();
4256 if (NS_FAILED(rv
)) {
4258 "HTMLEditor::MaybeExtendSelectionToHardLineEdgesForBlockEditAction() "
4264 // HandleCSSIndentAtSelectionInternal() creates AutoSelectionRestorer.
4265 // Therefore, even if it returns NS_OK, editor might have been destroyed
4266 // at restoring Selection.
4267 nsresult rv
= HandleCSSIndentAtSelectionInternal();
4268 if (NS_WARN_IF(Destroyed())) {
4269 return NS_ERROR_EDITOR_DESTROYED
;
4271 NS_WARNING_ASSERTION(
4273 "HTMLEditor::HandleCSSIndentAtSelectionInternal() failed");
4277 nsresult
HTMLEditor::HandleCSSIndentAtSelectionInternal() {
4278 MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable());
4279 MOZ_ASSERT(!IsSelectionRangeContainerNotContent());
4281 RefPtr
<Element
> editingHost
= ComputeEditingHost();
4282 if (MOZ_UNLIKELY(NS_WARN_IF(!editingHost
))) {
4283 return NS_ERROR_FAILURE
;
4286 AutoSelectionRestorer
restoreSelectionLater(*this);
4287 AutoTArray
<OwningNonNull
<nsIContent
>, 64> arrayOfContents
;
4289 // short circuit: detect case of collapsed selection inside an <li>.
4290 // just sublist that <li>. This prevents bug 97797.
4292 if (SelectionRef().IsCollapsed()) {
4293 const auto atCaret
= GetFirstSelectionStartPoint
<EditorRawDOMPoint
>();
4294 if (NS_WARN_IF(!atCaret
.IsSet())) {
4295 return NS_ERROR_FAILURE
;
4297 MOZ_ASSERT(atCaret
.IsInContentNode());
4298 Element
* const editableBlockElement
=
4299 HTMLEditUtils::GetInclusiveAncestorElement(
4300 *atCaret
.ContainerAsContent(),
4301 HTMLEditUtils::ClosestEditableBlockElement
);
4302 if (editableBlockElement
&&
4303 HTMLEditUtils::IsListItem(editableBlockElement
)) {
4304 arrayOfContents
.AppendElement(*editableBlockElement
);
4308 if (arrayOfContents
.IsEmpty()) {
4310 SplitInlinesAndCollectEditTargetNodesInExtendedSelectionRanges(
4311 arrayOfContents
, EditSubAction::eIndent
,
4312 CollectNonEditableNodes::Yes
);
4313 if (NS_FAILED(rv
)) {
4316 "SplitInlinesAndCollectEditTargetNodesInExtendedSelectionRanges("
4317 "eIndent, CollectNonEditableNodes::Yes) failed");
4322 // If there is no visible and editable nodes in the edit targets, make an
4324 // XXX Isn't this odd if there are only non-editable visible nodes?
4325 if (HTMLEditUtils::IsEmptyOneHardLine(arrayOfContents
)) {
4326 // get selection location
4327 const nsRange
* firstRange
= SelectionRef().GetRangeAt(0);
4328 if (NS_WARN_IF(!firstRange
)) {
4329 return NS_ERROR_FAILURE
;
4332 EditorDOMPoint
atStartOfSelection(firstRange
->StartRef());
4333 if (NS_WARN_IF(!atStartOfSelection
.IsSet())) {
4334 return NS_ERROR_FAILURE
;
4337 // make sure we can put a block here
4338 CreateElementResult createNewDivElementResult
=
4339 InsertElementWithSplittingAncestorsWithTransaction(
4340 *nsGkAtoms::div
, atStartOfSelection
,
4341 BRElementNextToSplitPoint::Keep
, *editingHost
);
4342 if (createNewDivElementResult
.isErr()) {
4344 "HTMLEditor::InsertElementWithSplittingAncestorsWithTransaction("
4345 "nsGkAtoms::div) failed");
4346 return createNewDivElementResult
.unwrapErr();
4348 // We'll update selection below, and nobody refers selection until then.
4349 // Therefore, we don't need to touch selection here.
4350 createNewDivElementResult
.IgnoreCaretPointSuggestion();
4351 const RefPtr
<Element
> newDivElement
=
4352 createNewDivElementResult
.UnwrapNewNode();
4353 MOZ_ASSERT(newDivElement
);
4354 // remember our new block for postprocessing
4355 TopLevelEditSubActionDataRef().mNewBlockElement
= newDivElement
;
4356 nsresult rv
= ChangeMarginStart(*newDivElement
, ChangeMargin::Increase
);
4357 if (NS_WARN_IF(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
4358 return NS_ERROR_EDITOR_DESTROYED
;
4360 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
4361 "HTMLEditor::ChangeMarginStart() failed, but ignored");
4362 // delete anything that was in the list of nodes
4363 // XXX We don't need to remove the nodes from the array for performance.
4364 while (!arrayOfContents
.IsEmpty()) {
4365 OwningNonNull
<nsIContent
>& content
= arrayOfContents
[0];
4366 // MOZ_KnownLive because 'arrayOfContents' is guaranteed to
4368 nsresult rv
= DeleteNodeWithTransaction(MOZ_KnownLive(*content
));
4369 if (NS_FAILED(rv
)) {
4370 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
4373 arrayOfContents
.RemoveElementAt(0);
4375 // Don't restore the selection
4376 restoreSelectionLater
.Abort();
4377 // put selection in new block
4378 rv
= CollapseSelectionToStartOf(*newDivElement
);
4379 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
4380 "EditorBase::CollapseSelectionToStartOf() failed");
4384 // Ok, now go through all the nodes and put them in a blockquote,
4385 // or whatever is appropriate.
4386 RefPtr
<Element
> curList
, curQuote
;
4387 for (OwningNonNull
<nsIContent
>& content
: arrayOfContents
) {
4388 // Here's where we actually figure out what to do.
4389 EditorDOMPoint
atContent(content
);
4390 if (NS_WARN_IF(!atContent
.IsSet())) {
4394 // Ignore all non-editable nodes. Leave them be.
4395 // XXX We ignore non-editable nodes here, but not so in the above block.
4396 if (!EditorUtils::IsEditableContent(content
, EditorType::HTML
)) {
4400 if (HTMLEditUtils::IsAnyListElement(atContent
.GetContainer())) {
4401 // MOZ_KnownLive because 'arrayOfContents' is guaranteed to
4404 IndentListChild(&curList
, atContent
, MOZ_KnownLive(content
));
4405 if (NS_FAILED(rv
)) {
4406 NS_WARNING("HTMLEditor::IndentListChild() failed");
4414 if (HTMLEditUtils::IsBlockElement(content
)) {
4415 nsresult rv
= ChangeMarginStart(MOZ_KnownLive(*content
->AsElement()),
4416 ChangeMargin::Increase
);
4417 if (NS_WARN_IF(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
4418 return NS_ERROR_EDITOR_DESTROYED
;
4420 NS_WARNING_ASSERTION(
4422 "HTMLEditor::ChangeMarginStart() failed, but ignored");
4428 // First, check that our element can contain a div.
4429 if (!HTMLEditUtils::CanNodeContain(*atContent
.GetContainer(),
4431 return NS_OK
; // cancelled
4434 CreateElementResult createNewDivElementResult
=
4435 InsertElementWithSplittingAncestorsWithTransaction(
4436 *nsGkAtoms::div
, atContent
, BRElementNextToSplitPoint::Keep
,
4438 if (createNewDivElementResult
.isErr()) {
4440 "HTMLEditor::InsertElementWithSplittingAncestorsWithTransaction("
4441 "nsGkAtoms::div) failed");
4442 return createNewDivElementResult
.unwrapErr();
4444 nsresult rv
= createNewDivElementResult
.SuggestCaretPointTo(
4445 *this, {SuggestCaret::OnlyIfHasSuggestion
,
4446 SuggestCaret::OnlyIfTransactionsAllowedToDoIt
});
4447 if (NS_FAILED(rv
)) {
4448 NS_WARNING("CreateElementResult::SuggestCaretPointTo() failed");
4451 RefPtr
<Element
> newDivElement
= createNewDivElementResult
.UnwrapNewNode();
4452 MOZ_ASSERT(newDivElement
);
4453 rv
= ChangeMarginStart(*newDivElement
, ChangeMargin::Increase
);
4454 if (NS_WARN_IF(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
4455 return NS_ERROR_EDITOR_DESTROYED
;
4457 NS_WARNING_ASSERTION(
4459 "HTMLEditor::ChangeMarginStart() failed, but ignored");
4460 // remember our new block for postprocessing
4461 TopLevelEditSubActionDataRef().mNewBlockElement
= newDivElement
;
4462 curQuote
= std::move(newDivElement
);
4463 // curQuote is now the correct thing to put content in
4466 // tuck the node into the end of the active blockquote
4467 // MOZ_KnownLive because 'arrayOfContents' is guaranteed to
4469 const MoveNodeResult moveNodeResult
=
4470 MoveNodeToEndWithTransaction(MOZ_KnownLive(content
), *curQuote
);
4471 if (moveNodeResult
.isErr()) {
4472 NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed");
4473 return moveNodeResult
.unwrapErr();
4475 nsresult rv
= moveNodeResult
.SuggestCaretPointTo(
4476 *this, {SuggestCaret::OnlyIfHasSuggestion
,
4477 SuggestCaret::OnlyIfTransactionsAllowedToDoIt
,
4478 SuggestCaret::AndIgnoreTrivialError
});
4479 if (NS_FAILED(rv
)) {
4480 NS_WARNING("MoveNodeResult::SuggestCaretPointTo() failed");
4483 NS_WARNING_ASSERTION(
4484 rv
!= NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR
,
4485 "MoveNodeResult::SuggestCaretPointTo() failed, but ignored");
4490 nsresult
HTMLEditor::HandleHTMLIndentAtSelection() {
4491 MOZ_ASSERT(IsEditActionDataAvailable());
4492 MOZ_ASSERT(!IsSelectionRangeContainerNotContent());
4494 if (!SelectionRef().IsCollapsed()) {
4495 nsresult rv
= MaybeExtendSelectionToHardLineEdgesForBlockEditAction();
4496 if (NS_FAILED(rv
)) {
4498 "HTMLEditor::MaybeExtendSelectionToHardLineEdgesForBlockEditAction() "
4504 // HandleHTMLIndentAtSelectionInternal() creates AutoSelectionRestorer.
4505 // Therefore, even if it returns NS_OK, editor might have been destroyed
4506 // at restoring Selection.
4507 nsresult rv
= HandleHTMLIndentAtSelectionInternal();
4508 if (NS_WARN_IF(Destroyed())) {
4509 return NS_ERROR_EDITOR_DESTROYED
;
4511 NS_WARNING_ASSERTION(
4513 "HTMLEditor::HandleHTMLIndentAtSelectionInternal() failed");
4517 nsresult
HTMLEditor::HandleHTMLIndentAtSelectionInternal() {
4518 MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable());
4520 RefPtr
<Element
> editingHost
= ComputeEditingHost();
4521 if (MOZ_UNLIKELY(NS_WARN_IF(!editingHost
))) {
4522 return NS_ERROR_FAILURE
;
4525 AutoSelectionRestorer
restoreSelectionLater(*this);
4527 // convert the selection ranges into "promoted" selection ranges:
4528 // this basically just expands the range to include the immediate
4529 // block parent, and then further expands to include any ancestors
4530 // whose children are all in the range
4532 AutoTArray
<RefPtr
<nsRange
>, 4> arrayOfRanges
;
4533 GetSelectionRangesExtendedToHardLineStartAndEnd(arrayOfRanges
,
4534 EditSubAction::eIndent
);
4536 // use these ranges to construct a list of nodes to act on.
4537 AutoTArray
<OwningNonNull
<nsIContent
>, 64> arrayOfContents
;
4538 nsresult rv
= SplitInlinesAndCollectEditTargetNodes(
4539 arrayOfRanges
, arrayOfContents
, EditSubAction::eIndent
,
4540 CollectNonEditableNodes::Yes
);
4541 if (NS_FAILED(rv
)) {
4543 "HTMLEditor::SplitInlinesAndCollectEditTargetNodes(eIndent, "
4544 "CollectNonEditableNodes::Yes) failed");
4548 // If there is no visible and editable nodes in the edit targets, make an
4550 // XXX Isn't this odd if there are only non-editable visible nodes?
4551 if (HTMLEditUtils::IsEmptyOneHardLine(arrayOfContents
)) {
4552 const nsRange
* firstRange
= SelectionRef().GetRangeAt(0);
4553 if (NS_WARN_IF(!firstRange
)) {
4554 return NS_ERROR_FAILURE
;
4557 EditorDOMPoint
atStartOfSelection(firstRange
->StartRef());
4558 if (NS_WARN_IF(!atStartOfSelection
.IsSet())) {
4559 return NS_ERROR_FAILURE
;
4562 // Make sure we can put a block here.
4563 CreateElementResult createNewBlockQuoteElementResult
=
4564 InsertElementWithSplittingAncestorsWithTransaction(
4565 *nsGkAtoms::blockquote
, atStartOfSelection
,
4566 BRElementNextToSplitPoint::Keep
, *editingHost
);
4567 if (createNewBlockQuoteElementResult
.isErr()) {
4569 "HTMLEditor::InsertElementWithSplittingAncestorsWithTransaction("
4570 "nsGkAtoms::blockquote) failed");
4571 return createNewBlockQuoteElementResult
.unwrapErr();
4573 // We'll update selection below after deleting the content nodes and nobody
4574 // refers selection until then. Therefore, we don't need to update
4576 createNewBlockQuoteElementResult
.IgnoreCaretPointSuggestion();
4577 RefPtr
<Element
> newBlockQuoteElement
=
4578 createNewBlockQuoteElementResult
.UnwrapNewNode();
4579 MOZ_ASSERT(newBlockQuoteElement
);
4580 // remember our new block for postprocessing
4581 TopLevelEditSubActionDataRef().mNewBlockElement
= newBlockQuoteElement
;
4582 // delete anything that was in the list of nodes
4583 // XXX We don't need to remove the nodes from the array for performance.
4584 for (const OwningNonNull
<nsIContent
>& content
: arrayOfContents
) {
4585 // MOZ_KnownLive because 'arrayOfContents' is guaranteed to
4587 nsresult rv
= DeleteNodeWithTransaction(MOZ_KnownLive(*content
));
4588 if (NS_FAILED(rv
)) {
4589 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
4593 // Don't restore the selection
4594 restoreSelectionLater
.Abort();
4595 nsresult rv
= CollapseSelectionToStartOf(*newBlockQuoteElement
);
4596 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
4597 "EditorBase::CollapseSelectionToStartOf() failed");
4601 // Ok, now go through all the nodes and put them in a blockquote,
4602 // or whatever is appropriate. Wohoo!
4603 RefPtr
<Element
> curList
, curQuote
, indentedLI
;
4604 for (OwningNonNull
<nsIContent
>& content
: arrayOfContents
) {
4605 // Here's where we actually figure out what to do.
4606 EditorDOMPoint
atContent(content
);
4607 if (NS_WARN_IF(!atContent
.IsSet())) {
4611 // Ignore all non-editable nodes. Leave them be.
4612 // XXX We ignore non-editable nodes here, but not so in the above block.
4613 if (!EditorUtils::IsEditableContent(content
, EditorType::HTML
)) {
4617 if (HTMLEditUtils::IsAnyListElement(atContent
.GetContainer())) {
4618 // MOZ_KnownLive because 'arrayOfContents' is guaranteed to
4621 IndentListChild(&curList
, atContent
, MOZ_KnownLive(content
));
4622 if (NS_FAILED(rv
)) {
4623 NS_WARNING("HTMLEditor::IndentListChild() failed");
4626 // forget curQuote, if any
4631 // Not a list item, use blockquote?
4633 // if we are inside a list item, we don't want to blockquote, we want
4634 // to sublist the list item. We may have several nodes listed in the
4635 // array of nodes to act on, that are in the same list item. Since
4636 // we only want to indent that li once, we must keep track of the most
4637 // recent indented list item, and not indent it if we find another node
4638 // to act on that is still inside the same li.
4639 if (RefPtr
<Element
> listItem
=
4640 HTMLEditUtils::GetClosestAncestorListItemElement(content
,
4642 if (indentedLI
== listItem
) {
4643 // already indented this list item
4646 // check to see if curList is still appropriate. Which it is if
4647 // content is still right after it in the same list.
4648 nsIContent
* previousEditableSibling
=
4649 curList
? HTMLEditUtils::GetPreviousSibling(
4650 *listItem
, {WalkTreeOption::IgnoreNonEditableNode
})
4653 (previousEditableSibling
&& previousEditableSibling
!= curList
)) {
4654 EditorDOMPoint
atListItem(listItem
);
4655 if (NS_WARN_IF(!listItem
)) {
4656 return NS_ERROR_FAILURE
;
4658 nsAtom
* containerName
=
4659 atListItem
.GetContainer()->NodeInfo()->NameAtom();
4660 // Create a new nested list of correct type.
4661 CreateElementResult createNewListElementResult
=
4662 InsertElementWithSplittingAncestorsWithTransaction(
4663 MOZ_KnownLive(*containerName
), atListItem
,
4664 BRElementNextToSplitPoint::Keep
, *editingHost
);
4665 if (createNewListElementResult
.isErr()) {
4666 NS_WARNING(nsPrintfCString("HTMLEditor::"
4667 "InsertElementWithSplittingAncestorsWithTr"
4668 "ansaction(%s) failed",
4669 nsAtomCString(containerName
).get())
4671 return createNewListElementResult
.unwrapErr();
4673 nsresult rv
= createNewListElementResult
.SuggestCaretPointTo(
4674 *this, {SuggestCaret::OnlyIfHasSuggestion
,
4675 SuggestCaret::OnlyIfTransactionsAllowedToDoIt
});
4676 if (NS_FAILED(rv
)) {
4677 NS_WARNING("CreateElementResult::SuggestCaretPointTo() failed");
4680 MOZ_ASSERT(createNewListElementResult
.GetNewNode());
4681 curList
= createNewListElementResult
.UnwrapNewNode();
4684 const MoveNodeResult moveListItemElementResult
=
4685 MoveNodeToEndWithTransaction(*listItem
, *curList
);
4686 if (moveListItemElementResult
.isErr()) {
4687 NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed");
4688 return moveListItemElementResult
.unwrapErr();
4690 nsresult rv
= moveListItemElementResult
.SuggestCaretPointTo(
4691 *this, {SuggestCaret::OnlyIfHasSuggestion
,
4692 SuggestCaret::OnlyIfTransactionsAllowedToDoIt
,
4693 SuggestCaret::AndIgnoreTrivialError
});
4694 if (NS_FAILED(rv
)) {
4695 NS_WARNING("MoveNodeResult::SuggestCaretPointTo() failed");
4698 NS_WARNING_ASSERTION(
4699 rv
!= NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR
,
4700 "MoveNodeResult::SuggestCaretPointTo() failed, but ignored");
4702 // remember we indented this li
4703 indentedLI
= listItem
;
4708 // need to make a blockquote to put things in if we haven't already,
4709 // or if this node doesn't go in blockquote we used earlier.
4710 // One reason it might not go in prio blockquote is if we are now
4711 // in a different table cell.
4713 HTMLEditUtils::GetInclusiveAncestorAnyTableElement(*curQuote
) !=
4714 HTMLEditUtils::GetInclusiveAncestorAnyTableElement(content
)) {
4719 // First, check that our element can contain a blockquote.
4720 if (!HTMLEditUtils::CanNodeContain(*atContent
.GetContainer(),
4721 *nsGkAtoms::blockquote
)) {
4722 return NS_OK
; // cancelled
4725 CreateElementResult createNewBlockQuoteElementResult
=
4726 InsertElementWithSplittingAncestorsWithTransaction(
4727 *nsGkAtoms::blockquote
, atContent
,
4728 BRElementNextToSplitPoint::Keep
, *editingHost
);
4729 if (createNewBlockQuoteElementResult
.isErr()) {
4731 "HTMLEditor::InsertElementWithSplittingAncestorsWithTransaction("
4732 "nsGkAtoms::blockquote) failed");
4733 return createNewBlockQuoteElementResult
.unwrapErr();
4735 nsresult rv
= createNewBlockQuoteElementResult
.SuggestCaretPointTo(
4736 *this, {SuggestCaret::OnlyIfHasSuggestion
,
4737 SuggestCaret::OnlyIfTransactionsAllowedToDoIt
});
4738 if (NS_FAILED(rv
)) {
4739 NS_WARNING("CreateElementResult::SuggestCaretPointTo() failed");
4743 RefPtr
<Element
> newBlockQuoteElement
=
4744 createNewBlockQuoteElementResult
.UnwrapNewNode();
4745 MOZ_ASSERT(newBlockQuoteElement
);
4746 // remember our new block for postprocessing
4747 TopLevelEditSubActionDataRef().mNewBlockElement
= newBlockQuoteElement
;
4748 curQuote
= std::move(newBlockQuoteElement
);
4749 // curQuote is now the correct thing to put curNode in
4752 // tuck the node into the end of the active blockquote
4753 // MOZ_KnownLive because 'arrayOfContents' is guaranteed to
4755 const MoveNodeResult moveNodeResult
=
4756 MoveNodeToEndWithTransaction(MOZ_KnownLive(content
), *curQuote
);
4757 if (moveNodeResult
.isErr()) {
4758 NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed");
4759 return moveNodeResult
.unwrapErr();
4761 nsresult rv
= moveNodeResult
.SuggestCaretPointTo(
4762 *this, {SuggestCaret::OnlyIfHasSuggestion
,
4763 SuggestCaret::OnlyIfTransactionsAllowedToDoIt
,
4764 SuggestCaret::AndIgnoreTrivialError
});
4765 if (NS_FAILED(rv
)) {
4766 NS_WARNING("MoveNodeResult::SuggestCaretPointTo() failed");
4769 NS_WARNING_ASSERTION(
4770 rv
!= NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR
,
4771 "MoveNodeResult::SuggestCaretPointTo() failed, but ignored");
4772 // forget curList, if any
4778 EditActionResult
HTMLEditor::OutdentAsSubAction() {
4779 MOZ_ASSERT(IsEditActionDataAvailable());
4781 AutoPlaceholderBatch
treatAsOneTransaction(
4782 *this, ScrollSelectionIntoView::Yes
, __FUNCTION__
);
4783 IgnoredErrorResult ignoredError
;
4784 AutoEditSubActionNotifier
startToHandleEditSubAction(
4785 *this, EditSubAction::eOutdent
, nsIEditor::eNext
, ignoredError
);
4786 if (NS_WARN_IF(ignoredError
.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED
))) {
4787 return EditActionResult(ignoredError
.StealNSResult());
4789 NS_WARNING_ASSERTION(
4790 !ignoredError
.Failed(),
4791 "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
4793 EditActionResult result
= CanHandleHTMLEditSubAction();
4794 if (result
.Failed() || result
.Canceled()) {
4795 NS_WARNING_ASSERTION(result
.Succeeded(),
4796 "HTMLEditor::CanHandleHTMLEditSubAction() failed");
4800 if (IsSelectionRangeContainerNotContent()) {
4801 NS_WARNING("Some selection containers are not content node, but ignored");
4802 return EditActionIgnored();
4805 result
|= HandleOutdentAtSelection();
4806 if (result
.Failed() || result
.Canceled()) {
4807 NS_WARNING_ASSERTION(result
.Succeeded(),
4808 "HTMLEditor::HandleOutdentAtSelection() failed");
4812 if (IsSelectionRangeContainerNotContent()) {
4813 NS_WARNING("Mutation event listener might have changed the selection");
4814 return EditActionHandled(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
);
4817 nsresult rv
= MaybeInsertPaddingBRElementForEmptyLastLineAtSelection();
4818 NS_WARNING_ASSERTION(
4820 "HTMLEditor::MaybeInsertPaddingBRElementForEmptyLastLineAtSelection() "
4822 return result
.SetResult(rv
);
4825 EditActionResult
HTMLEditor::HandleOutdentAtSelection() {
4826 MOZ_ASSERT(IsEditActionDataAvailable());
4827 MOZ_ASSERT(!IsSelectionRangeContainerNotContent());
4829 if (!SelectionRef().IsCollapsed()) {
4830 nsresult rv
= MaybeExtendSelectionToHardLineEdgesForBlockEditAction();
4831 if (NS_WARN_IF(NS_FAILED(rv
))) {
4832 return EditActionHandled(rv
);
4836 // HandleOutdentAtSelectionInternal() creates AutoSelectionRestorer.
4837 // Therefore, even if it returns NS_OK, the editor might have been destroyed
4838 // at restoring Selection.
4839 SplitRangeOffFromNodeResult outdentResult
=
4840 HandleOutdentAtSelectionInternal();
4841 if (NS_WARN_IF(Destroyed())) {
4842 return EditActionHandled(NS_ERROR_EDITOR_DESTROYED
);
4844 if (outdentResult
.isErr()) {
4845 NS_WARNING("HTMLEditor::HandleOutdentAtSelectionInternal() failed");
4846 return EditActionHandled(outdentResult
.unwrapErr());
4849 // Make sure selection didn't stick to last piece of content in old bq (only
4850 // a problem for collapsed selections)
4851 if (!outdentResult
.GetLeftContent() && !outdentResult
.GetRightContent()) {
4852 return EditActionHandled();
4855 if (!SelectionRef().IsCollapsed()) {
4856 return EditActionHandled();
4859 // Push selection past end of left element of last split indented element.
4860 if (outdentResult
.GetLeftContent()) {
4861 const nsRange
* firstRange
= SelectionRef().GetRangeAt(0);
4862 if (NS_WARN_IF(!firstRange
)) {
4863 return EditActionHandled();
4865 const RangeBoundary
& atStartOfSelection
= firstRange
->StartRef();
4866 if (NS_WARN_IF(!atStartOfSelection
.IsSet())) {
4867 return EditActionHandled(NS_ERROR_FAILURE
);
4869 if (atStartOfSelection
.Container() == outdentResult
.GetLeftContent() ||
4870 EditorUtils::IsDescendantOf(*atStartOfSelection
.Container(),
4871 *outdentResult
.GetLeftContent())) {
4872 // Selection is inside the left node - push it past it.
4873 EditorRawDOMPoint
afterRememberedLeftBQ(
4874 EditorRawDOMPoint::After(*outdentResult
.GetLeftContent()));
4875 NS_WARNING_ASSERTION(
4876 afterRememberedLeftBQ
.IsSet(),
4877 "Failed to set after remembered left blockquote element");
4878 nsresult rv
= CollapseSelectionTo(afterRememberedLeftBQ
);
4879 if (MOZ_UNLIKELY(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
4881 "EditorBase::CollapseSelectionTo() caused destroying the editor");
4882 return EditActionHandled(NS_ERROR_EDITOR_DESTROYED
);
4884 NS_WARNING_ASSERTION(
4886 "EditorBase::CollapseSelectionTo() failed, but ignored");
4889 // And pull selection before beginning of right element of last split
4890 // indented element.
4891 if (outdentResult
.GetRightContent()) {
4892 const nsRange
* firstRange
= SelectionRef().GetRangeAt(0);
4893 if (NS_WARN_IF(!firstRange
)) {
4894 return EditActionHandled();
4896 const RangeBoundary
& atStartOfSelection
= firstRange
->StartRef();
4897 if (NS_WARN_IF(!atStartOfSelection
.IsSet())) {
4898 return EditActionHandled(NS_ERROR_FAILURE
);
4900 if (atStartOfSelection
.Container() == outdentResult
.GetRightContent() ||
4901 EditorUtils::IsDescendantOf(*atStartOfSelection
.Container(),
4902 *outdentResult
.GetRightContent())) {
4903 // Selection is inside the right element - push it before it.
4904 EditorRawDOMPoint
atRememberedRightBQ(outdentResult
.GetRightContent());
4905 nsresult rv
= CollapseSelectionTo(atRememberedRightBQ
);
4906 if (MOZ_UNLIKELY(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
4908 "EditorBase::CollapseSelectionTo() caused destroying the editor");
4909 return EditActionHandled(NS_ERROR_EDITOR_DESTROYED
);
4911 NS_WARNING_ASSERTION(
4913 "EditorBase::CollapseSelectionTo() failed, but ignored");
4916 return EditActionHandled();
4919 SplitRangeOffFromNodeResult
HTMLEditor::HandleOutdentAtSelectionInternal() {
4920 MOZ_ASSERT(IsEditActionDataAvailable());
4922 AutoSelectionRestorer
restoreSelectionLater(*this);
4924 bool useCSS
= IsCSSEnabled();
4926 // Convert the selection ranges into "promoted" selection ranges: this
4927 // basically just expands the range to include the immediate block parent,
4928 // and then further expands to include any ancestors whose children are all
4930 AutoTArray
<OwningNonNull
<nsIContent
>, 64> arrayOfContents
;
4931 nsresult rv
= SplitInlinesAndCollectEditTargetNodesInExtendedSelectionRanges(
4932 arrayOfContents
, EditSubAction::eOutdent
, CollectNonEditableNodes::Yes
);
4933 if (NS_FAILED(rv
)) {
4936 "SplitInlinesAndCollectEditTargetNodesInExtendedSelectionRanges() "
4938 return SplitRangeOffFromNodeResult(rv
);
4941 nsCOMPtr
<nsIContent
> leftContentOfLastOutdented
;
4942 nsCOMPtr
<nsIContent
> middleContentOfLastOutdented
;
4943 nsCOMPtr
<nsIContent
> rightContentOfLastOutdented
;
4944 RefPtr
<Element
> indentedParentElement
;
4945 nsCOMPtr
<nsIContent
> firstContentToBeOutdented
, lastContentToBeOutdented
;
4946 BlockIndentedWith indentedParentIndentedWith
= BlockIndentedWith::HTML
;
4947 for (OwningNonNull
<nsIContent
>& content
: arrayOfContents
) {
4948 // Here's where we actually figure out what to do
4949 EditorDOMPoint
atContent(content
);
4950 if (!atContent
.IsSet()) {
4954 // If it's a `<blockquote>`, remove it to outdent its children.
4955 if (content
->IsHTMLElement(nsGkAtoms::blockquote
)) {
4956 // If we've already found an ancestor block element indented, we need to
4957 // split it and remove the block element first.
4958 if (indentedParentElement
) {
4959 MOZ_ASSERT(indentedParentElement
== content
);
4960 SplitRangeOffFromNodeResult outdentResult
= OutdentPartOfBlock(
4961 *indentedParentElement
, *firstContentToBeOutdented
,
4962 *lastContentToBeOutdented
, indentedParentIndentedWith
);
4963 if (outdentResult
.isErr()) {
4964 NS_WARNING("HTMLEditor::OutdentPartOfBlock() failed");
4965 return outdentResult
;
4967 leftContentOfLastOutdented
= outdentResult
.GetLeftContent();
4968 middleContentOfLastOutdented
= outdentResult
.GetMiddleContent();
4969 rightContentOfLastOutdented
= outdentResult
.GetRightContent();
4970 indentedParentElement
= nullptr;
4971 firstContentToBeOutdented
= nullptr;
4972 lastContentToBeOutdented
= nullptr;
4973 indentedParentIndentedWith
= BlockIndentedWith::HTML
;
4975 const Result
<EditorDOMPoint
, nsresult
> unwrapBlockquoteElementResult
=
4976 RemoveBlockContainerWithTransaction(
4977 MOZ_KnownLive(*content
->AsElement()));
4978 if (MOZ_UNLIKELY(unwrapBlockquoteElementResult
.isErr())) {
4979 NS_WARNING("HTMLEditor::RemoveBlockContainerWithTransaction() failed");
4980 return SplitRangeOffFromNodeResult(
4981 unwrapBlockquoteElementResult
.inspectErr());
4983 const EditorDOMPoint
& pointToPutCaret
=
4984 unwrapBlockquoteElementResult
.inspect();
4985 if (AllowsTransactionsToChangeSelection() && pointToPutCaret
.IsSet()) {
4986 nsresult rv
= CollapseSelectionTo(pointToPutCaret
);
4987 if (NS_FAILED(rv
)) {
4988 NS_WARNING("EditorBase::CollapseSelectionTo() failed");
4989 return SplitRangeOffFromNodeResult(rv
);
4995 // If we're using CSS and the node is a block element, check its start
4996 // margin whether it's indented with CSS.
4997 if (useCSS
&& HTMLEditUtils::IsBlockElement(content
)) {
4998 nsStaticAtom
& marginProperty
=
4999 MarginPropertyAtomForIndent(MOZ_KnownLive(content
));
5000 if (NS_WARN_IF(Destroyed())) {
5001 return SplitRangeOffFromNodeResult(NS_ERROR_EDITOR_DESTROYED
);
5004 DebugOnly
<nsresult
> rvIgnored
=
5005 CSSEditUtils::GetSpecifiedProperty(content
, marginProperty
, value
);
5006 if (NS_WARN_IF(Destroyed())) {
5007 return SplitRangeOffFromNodeResult(NS_ERROR_EDITOR_DESTROYED
);
5009 NS_WARNING_ASSERTION(
5010 NS_SUCCEEDED(rvIgnored
),
5011 "CSSEditUtils::GetSpecifiedProperty() failed, but ignored");
5012 float startMargin
= 0;
5013 RefPtr
<nsAtom
> unit
;
5014 CSSEditUtils::ParseLength(value
, &startMargin
, getter_AddRefs(unit
));
5015 // If indented with CSS, we should decrease the start mergin.
5016 if (startMargin
> 0) {
5017 nsresult rv
= ChangeMarginStart(MOZ_KnownLive(*content
->AsElement()),
5018 ChangeMargin::Decrease
);
5019 if (NS_WARN_IF(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
5020 return SplitRangeOffFromNodeResult(NS_ERROR_EDITOR_DESTROYED
);
5022 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
5023 "HTMLEditor::ChangeMarginStart(ChangeMargin::"
5024 "Decrease) failed, but ignored");
5029 // If it's a list item, we should treat as that it "indents" its children.
5030 if (HTMLEditUtils::IsListItem(content
)) {
5031 // If it is a list item, that means we are not outdenting whole list.
5032 // XXX I don't understand this sentence... We may meet parent list
5034 if (indentedParentElement
) {
5035 SplitRangeOffFromNodeResult outdentResult
= OutdentPartOfBlock(
5036 *indentedParentElement
, *firstContentToBeOutdented
,
5037 *lastContentToBeOutdented
, indentedParentIndentedWith
);
5038 if (outdentResult
.isErr()) {
5039 NS_WARNING("HTMLEditor::OutdentPartOfBlock() failed");
5040 return outdentResult
;
5042 leftContentOfLastOutdented
= outdentResult
.GetLeftContent();
5043 middleContentOfLastOutdented
= outdentResult
.GetMiddleContent();
5044 rightContentOfLastOutdented
= outdentResult
.GetRightContent();
5045 indentedParentElement
= nullptr;
5046 firstContentToBeOutdented
= nullptr;
5047 lastContentToBeOutdented
= nullptr;
5048 indentedParentIndentedWith
= BlockIndentedWith::HTML
;
5050 // XXX `content` could become different element since
5051 // `OutdentPartOfBlock()` may run mutation event listeners.
5052 rv
= LiftUpListItemElement(MOZ_KnownLive(*content
->AsElement()),
5053 LiftUpFromAllParentListElements::No
);
5054 if (NS_FAILED(rv
)) {
5056 "HTMLEditor::LiftUpListItemElement(LiftUpFromAllParentListElements:"
5058 return SplitRangeOffFromNodeResult(rv
);
5063 // If we've found an ancestor block element which indents its children
5064 // and the current node is NOT a descendant of it, we should remove it to
5065 // outdent its children. Otherwise, i.e., current node is a descendant of
5066 // it, we meet new node which should be outdented when the indented parent
5068 if (indentedParentElement
) {
5069 if (EditorUtils::IsDescendantOf(*content
, *indentedParentElement
)) {
5070 // Extend the range to be outdented at removing the
5071 // indentedParentElement.
5072 lastContentToBeOutdented
= content
;
5075 SplitRangeOffFromNodeResult outdentResult
= OutdentPartOfBlock(
5076 *indentedParentElement
, *firstContentToBeOutdented
,
5077 *lastContentToBeOutdented
, indentedParentIndentedWith
);
5078 if (outdentResult
.isErr()) {
5079 NS_WARNING("HTMLEditor::OutdentPartOfBlock() failed");
5080 return outdentResult
;
5082 leftContentOfLastOutdented
= outdentResult
.GetLeftContent();
5083 middleContentOfLastOutdented
= outdentResult
.GetMiddleContent();
5084 rightContentOfLastOutdented
= outdentResult
.GetRightContent();
5085 indentedParentElement
= nullptr;
5086 firstContentToBeOutdented
= nullptr;
5087 lastContentToBeOutdented
= nullptr;
5088 // curBlockIndentedWith = HTMLEditor::BlockIndentedWith::HTML;
5090 // Then, we need to look for next indentedParentElement.
5093 indentedParentIndentedWith
= BlockIndentedWith::HTML
;
5094 RefPtr
<Element
> editingHost
= ComputeEditingHost();
5095 for (nsCOMPtr
<nsIContent
> parentContent
= content
->GetParent();
5096 parentContent
&& !parentContent
->IsHTMLElement(nsGkAtoms::body
) &&
5097 parentContent
!= editingHost
&&
5098 (parentContent
->IsHTMLElement(nsGkAtoms::table
) ||
5099 !HTMLEditUtils::IsAnyTableElement(parentContent
));
5100 parentContent
= parentContent
->GetParent()) {
5101 // If we reach a `<blockquote>` ancestor, it should be split at next
5102 // time at least for outdenting current node.
5103 if (parentContent
->IsHTMLElement(nsGkAtoms::blockquote
)) {
5104 indentedParentElement
= parentContent
->AsElement();
5105 firstContentToBeOutdented
= content
;
5106 lastContentToBeOutdented
= content
;
5114 nsCOMPtr
<nsINode
> grandParentNode
= parentContent
->GetParentNode();
5115 nsStaticAtom
& marginProperty
=
5116 MarginPropertyAtomForIndent(MOZ_KnownLive(content
));
5117 if (NS_WARN_IF(Destroyed())) {
5118 return SplitRangeOffFromNodeResult(NS_ERROR_EDITOR_DESTROYED
);
5120 if (NS_WARN_IF(grandParentNode
!= parentContent
->GetParentNode())) {
5121 return SplitRangeOffFromNodeResult(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
);
5124 DebugOnly
<nsresult
> rvIgnored
= CSSEditUtils::GetSpecifiedProperty(
5125 *parentContent
, marginProperty
, value
);
5126 if (NS_WARN_IF(Destroyed())) {
5127 return SplitRangeOffFromNodeResult(NS_ERROR_EDITOR_DESTROYED
);
5129 NS_WARNING_ASSERTION(
5130 NS_SUCCEEDED(rvIgnored
),
5131 "CSSEditUtils::GetSpecifiedProperty() failed, but ignored");
5132 // XXX Now, editing host may become different element. If so, shouldn't
5133 // we stop this handling?
5135 RefPtr
<nsAtom
> unit
;
5136 CSSEditUtils::ParseLength(value
, &startMargin
, getter_AddRefs(unit
));
5137 // If we reach a block element which indents its children with start
5138 // margin, we should remove it at next time.
5139 if (startMargin
> 0 &&
5140 !(HTMLEditUtils::IsAnyListElement(atContent
.GetContainer()) &&
5141 HTMLEditUtils::IsAnyListElement(content
))) {
5142 indentedParentElement
= parentContent
->AsElement();
5143 firstContentToBeOutdented
= content
;
5144 lastContentToBeOutdented
= content
;
5145 indentedParentIndentedWith
= BlockIndentedWith::CSS
;
5150 if (indentedParentElement
) {
5154 // If we don't have any block elements which indents current node and
5155 // both current node and its parent are list element, remove current
5156 // node to move all its children to the parent list.
5157 // XXX This is buggy. When both lists' item types are different,
5158 // we create invalid tree. E.g., `<ul>` may have `<dd>` as its
5159 // list item element.
5160 if (HTMLEditUtils::IsAnyListElement(atContent
.GetContainer())) {
5161 if (!HTMLEditUtils::IsAnyListElement(content
)) {
5164 // Just unwrap this sublist
5165 const Result
<EditorDOMPoint
, nsresult
> unwrapSubListElementResult
=
5166 RemoveBlockContainerWithTransaction(
5167 MOZ_KnownLive(*content
->AsElement()));
5168 if (MOZ_UNLIKELY(unwrapSubListElementResult
.isErr())) {
5169 NS_WARNING("HTMLEditor::RemoveBlockContainerWithTransaction() failed");
5170 return SplitRangeOffFromNodeResult(
5171 unwrapSubListElementResult
.inspectErr());
5173 const EditorDOMPoint
& pointToPutCaret
=
5174 unwrapSubListElementResult
.inspect();
5175 if (!AllowsTransactionsToChangeSelection() || !pointToPutCaret
.IsSet()) {
5178 nsresult rv
= CollapseSelectionTo(pointToPutCaret
);
5179 if (NS_FAILED(rv
)) {
5180 NS_WARNING("EditorBase::CollapseSelectionTo() failed");
5181 return SplitRangeOffFromNodeResult(rv
);
5186 // If current content is a list element but its parent is not a list
5187 // element, move children to where it is and remove it from the tree.
5188 if (HTMLEditUtils::IsAnyListElement(content
)) {
5189 // XXX If mutation event listener appends new children forever, this
5190 // becomes an infinite loop so that we should set limitation from
5191 // first child count.
5192 for (nsCOMPtr
<nsIContent
> lastChildContent
= content
->GetLastChild();
5193 lastChildContent
; lastChildContent
= content
->GetLastChild()) {
5194 if (HTMLEditUtils::IsListItem(lastChildContent
)) {
5195 nsresult rv
= LiftUpListItemElement(
5196 MOZ_KnownLive(*lastChildContent
->AsElement()),
5197 LiftUpFromAllParentListElements::No
);
5198 if (NS_FAILED(rv
)) {
5200 "HTMLEditor::LiftUpListItemElement("
5201 "LiftUpFromAllParentListElements::No) failed");
5202 return SplitRangeOffFromNodeResult(rv
);
5207 if (HTMLEditUtils::IsAnyListElement(lastChildContent
)) {
5208 // We have an embedded list, so move it out from under the parent
5209 // list. Be sure to put it after the parent list because this
5210 // loop iterates backwards through the parent's list of children.
5211 EditorDOMPoint
afterCurrentList(EditorDOMPoint::After(atContent
));
5212 NS_WARNING_ASSERTION(
5213 afterCurrentList
.IsSet(),
5214 "Failed to set it to after current list element");
5215 const MoveNodeResult moveListElementResult
=
5216 MoveNodeWithTransaction(*lastChildContent
, afterCurrentList
);
5217 if (moveListElementResult
.isErr()) {
5218 NS_WARNING("HTMLEditor::MoveNodeWithTransaction() failed");
5219 return SplitRangeOffFromNodeResult(
5220 moveListElementResult
.unwrapErr());
5222 nsresult rv
= moveListElementResult
.SuggestCaretPointTo(
5223 *this, {SuggestCaret::OnlyIfHasSuggestion
,
5224 SuggestCaret::OnlyIfTransactionsAllowedToDoIt
,
5225 SuggestCaret::AndIgnoreTrivialError
});
5226 if (NS_FAILED(rv
)) {
5227 NS_WARNING("MoveNodeResult::SuggestCaretPointTo() failed");
5228 return SplitRangeOffFromNodeResult(rv
);
5230 NS_WARNING_ASSERTION(
5231 rv
!= NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR
,
5232 "MoveNodeResult::SuggestCaretPointTo() failed, but ignored");
5236 // Delete any non-list items for now
5237 // XXX Chrome moves it from the list element. We should follow it.
5238 nsresult rv
= DeleteNodeWithTransaction(*lastChildContent
);
5239 if (NS_FAILED(rv
)) {
5240 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
5241 return SplitRangeOffFromNodeResult(rv
);
5244 // Delete the now-empty list
5245 const Result
<EditorDOMPoint
, nsresult
> unwrapListElementResult
=
5246 RemoveBlockContainerWithTransaction(
5247 MOZ_KnownLive(*content
->AsElement()));
5248 if (MOZ_UNLIKELY(unwrapListElementResult
.isErr())) {
5249 NS_WARNING("HTMLEditor::RemoveBlockContainerWithTransaction() failed");
5250 return SplitRangeOffFromNodeResult(
5251 unwrapListElementResult
.inspectErr());
5253 const EditorDOMPoint
& pointToPutCaret
= unwrapListElementResult
.inspect();
5254 if (!AllowsTransactionsToChangeSelection() || !pointToPutCaret
.IsSet()) {
5257 nsresult rv
= CollapseSelectionTo(pointToPutCaret
);
5258 if (NS_FAILED(rv
)) {
5259 NS_WARNING("EditorBase::CollapseSelectionTo() failed");
5260 return SplitRangeOffFromNodeResult(rv
);
5266 if (RefPtr
<Element
> element
= content
->GetAsElementOrParentElement()) {
5267 nsresult rv
= ChangeMarginStart(*element
, ChangeMargin::Decrease
);
5268 if (NS_WARN_IF(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
5269 return SplitRangeOffFromNodeResult(NS_ERROR_EDITOR_DESTROYED
);
5271 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
5272 "HTMLEditor::ChangeMarginStart(ChangeMargin::"
5273 "Decrease) failed, but ignored");
5279 if (!indentedParentElement
) {
5280 return SplitRangeOffFromNodeResult(leftContentOfLastOutdented
,
5281 middleContentOfLastOutdented
,
5282 rightContentOfLastOutdented
);
5285 // We have a <blockquote> we haven't finished handling.
5286 SplitRangeOffFromNodeResult outdentResult
=
5287 OutdentPartOfBlock(*indentedParentElement
, *firstContentToBeOutdented
,
5288 *lastContentToBeOutdented
, indentedParentIndentedWith
);
5289 NS_WARNING_ASSERTION(outdentResult
.isOk(),
5290 "HTMLEditor::OutdentPartOfBlock() failed");
5291 return outdentResult
;
5294 SplitRangeOffFromNodeResult
5295 HTMLEditor::SplitRangeOffFromBlockAndRemoveMiddleContainer(
5296 Element
& aBlockElement
, nsIContent
& aStartOfRange
,
5297 nsIContent
& aEndOfRange
) {
5298 MOZ_ASSERT(IsEditActionDataAvailable());
5300 SplitRangeOffFromNodeResult splitResult
=
5301 SplitRangeOffFromBlock(aBlockElement
, aStartOfRange
, aEndOfRange
);
5302 if (splitResult
.EditorDestroyed()) {
5304 "HTMLEditor::SplitRangeOffFromBlock() caused destorying the editor");
5307 NS_WARNING_ASSERTION(
5309 "HTMLEditor::SplitRangeOffFromBlock() failed, but might be ignored");
5310 const Result
<EditorDOMPoint
, nsresult
> unwrapBlockElementResult
=
5311 RemoveBlockContainerWithTransaction(aBlockElement
);
5312 if (MOZ_UNLIKELY(unwrapBlockElementResult
.isErr())) {
5313 NS_WARNING("HTMLEditor::RemoveBlockContainerWithTransaction() failed");
5314 return SplitRangeOffFromNodeResult(unwrapBlockElementResult
.inspectErr());
5316 const EditorDOMPoint
& pointToPutCaret
= unwrapBlockElementResult
.inspect();
5317 if (AllowsTransactionsToChangeSelection() && pointToPutCaret
.IsSet()) {
5318 nsresult rv
= CollapseSelectionTo(pointToPutCaret
);
5319 if (NS_FAILED(rv
)) {
5320 return SplitRangeOffFromNodeResult(rv
);
5323 return SplitRangeOffFromNodeResult(splitResult
.GetLeftContent(), nullptr,
5324 splitResult
.GetRightContent());
5327 SplitRangeOffFromNodeResult
HTMLEditor::SplitRangeOffFromBlock(
5328 Element
& aBlockElement
, nsIContent
& aStartOfMiddleElement
,
5329 nsIContent
& aEndOfMiddleElement
) {
5330 MOZ_ASSERT(IsEditActionDataAvailable());
5332 // aStartOfMiddleElement and aEndOfMiddleElement must be exclusive
5333 // descendants of aBlockElement.
5334 MOZ_ASSERT(EditorUtils::IsDescendantOf(aStartOfMiddleElement
, aBlockElement
));
5335 MOZ_ASSERT(EditorUtils::IsDescendantOf(aEndOfMiddleElement
, aBlockElement
));
5337 // Split at the start.
5338 SplitNodeResult splitAtStartResult
= SplitNodeDeepWithTransaction(
5339 aBlockElement
, EditorDOMPoint(&aStartOfMiddleElement
),
5340 SplitAtEdges::eDoNotCreateEmptyContainer
);
5341 if (MOZ_UNLIKELY(NS_WARN_IF(splitAtStartResult
.EditorDestroyed()))) {
5342 return SplitRangeOffFromNodeResult(NS_ERROR_EDITOR_DESTROYED
);
5344 NS_WARNING_ASSERTION(
5345 splitAtStartResult
.isOk(),
5346 "HTMLEditor::SplitNodeDeepWithTransaction(SplitAtEdges::"
5347 "eDoNotCreateEmptyContainer) at start of middle element failed");
5349 // Split at after the end
5350 auto atAfterEnd
= EditorDOMPoint::After(aEndOfMiddleElement
);
5351 SplitNodeResult splitAtEndResult
= SplitNodeDeepWithTransaction(
5352 aBlockElement
, atAfterEnd
, SplitAtEdges::eDoNotCreateEmptyContainer
);
5353 if (MOZ_UNLIKELY(NS_WARN_IF(splitAtEndResult
.EditorDestroyed()))) {
5354 return SplitRangeOffFromNodeResult(NS_ERROR_EDITOR_DESTROYED
);
5356 NS_WARNING_ASSERTION(
5357 splitAtEndResult
.isOk(),
5358 "HTMLEditor::SplitNodeDeepWithTransaction(SplitAtEdges::"
5359 "eDoNotCreateEmptyContainer) after end of middle element failed");
5361 if (AllowsTransactionsToChangeSelection() &&
5362 (splitAtStartResult
.HasCaretPointSuggestion() ||
5363 splitAtEndResult
.HasCaretPointSuggestion())) {
5364 const SplitNodeResult
& splitNodeResultHavingLatestCaretSuggestion
=
5365 [&]() -> SplitNodeResult
& {
5366 if (splitAtEndResult
.HasCaretPointSuggestion()) {
5367 splitAtStartResult
.IgnoreCaretPointSuggestion();
5368 return splitAtEndResult
;
5370 return splitAtStartResult
;
5373 splitNodeResultHavingLatestCaretSuggestion
.SuggestCaretPointTo(
5374 *this, {SuggestCaret::OnlyIfHasSuggestion
});
5375 if (NS_FAILED(rv
)) {
5376 NS_WARNING("SplitNodeResult::SuggestCaretPointTo() failed");
5377 return SplitRangeOffFromNodeResult(rv
);
5381 return SplitRangeOffFromNodeResult(std::move(splitAtStartResult
),
5382 std::move(splitAtEndResult
));
5385 SplitRangeOffFromNodeResult
HTMLEditor::OutdentPartOfBlock(
5386 Element
& aBlockElement
, nsIContent
& aStartOfOutdent
,
5387 nsIContent
& aEndOfOutdent
, BlockIndentedWith aBlockIndentedWith
) {
5388 MOZ_ASSERT(IsEditActionDataAvailable());
5390 SplitRangeOffFromNodeResult splitResult
=
5391 SplitRangeOffFromBlock(aBlockElement
, aStartOfOutdent
, aEndOfOutdent
);
5392 if (NS_WARN_IF(splitResult
.EditorDestroyed())) {
5393 return SplitRangeOffFromNodeResult(NS_ERROR_EDITOR_DESTROYED
);
5396 if (!splitResult
.GetMiddleContentAsElement()) {
5398 "HTMLEditor::SplitRangeOffFromBlock() didn't return middle content");
5399 return SplitRangeOffFromNodeResult(NS_ERROR_FAILURE
);
5401 NS_WARNING_ASSERTION(
5403 "HTMLEditor::SplitRangeOffFromBlock() failed, but might be ignored");
5405 if (aBlockIndentedWith
== BlockIndentedWith::HTML
) {
5406 Result
<EditorDOMPoint
, nsresult
> unwrapBlockElementResult
=
5407 RemoveBlockContainerWithTransaction(
5408 MOZ_KnownLive(*splitResult
.GetMiddleContentAsElement()));
5409 if (MOZ_UNLIKELY(unwrapBlockElementResult
.isErr())) {
5410 NS_WARNING("HTMLEditor::RemoveBlockContainerWithTransaction() failed");
5411 return SplitRangeOffFromNodeResult(unwrapBlockElementResult
.inspectErr());
5413 const EditorDOMPoint
& pointToPutCaret
= unwrapBlockElementResult
.inspect();
5414 if (AllowsTransactionsToChangeSelection() && pointToPutCaret
.IsSet()) {
5415 nsresult rv
= CollapseSelectionTo(pointToPutCaret
);
5416 if (NS_FAILED(rv
)) {
5417 NS_WARNING("EditorBase::CollapseSelectionTo() failed");
5418 return SplitRangeOffFromNodeResult(rv
);
5421 return SplitRangeOffFromNodeResult(splitResult
.GetLeftContent(), nullptr,
5422 splitResult
.GetRightContent());
5425 if (splitResult
.GetMiddleContentAsElement()) {
5426 nsresult rv
= ChangeMarginStart(
5427 MOZ_KnownLive(*splitResult
.GetMiddleContentAsElement()),
5428 ChangeMargin::Decrease
);
5429 if (NS_FAILED(rv
)) {
5431 "HTMLEditor::ChangeMarginStart(ChangeMargin::Decrease) failed");
5432 return SplitRangeOffFromNodeResult(rv
);
5440 CreateElementResult
HTMLEditor::ChangeListElementType(Element
& aListElement
,
5441 nsAtom
& aNewListTag
,
5442 nsAtom
& aNewListItemTag
) {
5443 MOZ_ASSERT(IsEditActionDataAvailable());
5445 for (nsIContent
* childContent
= aListElement
.GetFirstChild(); childContent
;
5446 childContent
= childContent
->GetNextSibling()) {
5447 if (!childContent
->IsElement()) {
5450 if (HTMLEditUtils::IsListItem(childContent
->AsElement()) &&
5451 !childContent
->IsHTMLElement(&aNewListItemTag
)) {
5452 OwningNonNull
<Element
> listItemElement
= *childContent
->AsElement();
5453 CreateElementResult newListItemElementOrError
=
5454 ReplaceContainerWithTransaction(listItemElement
, aNewListItemTag
);
5455 if (newListItemElementOrError
.isErr()) {
5456 NS_WARNING("HTMLEditor::ReplaceContainerWithTransaction() failed");
5457 return newListItemElementOrError
;
5459 nsresult rv
= newListItemElementOrError
.SuggestCaretPointTo(
5460 *this, {SuggestCaret::OnlyIfHasSuggestion
,
5461 SuggestCaret::OnlyIfTransactionsAllowedToDoIt
,
5462 SuggestCaret::AndIgnoreTrivialError
});
5463 if (NS_FAILED(rv
)) {
5464 NS_WARNING("CreateElementResult::SuggestCaretPointTo() failed");
5465 return CreateElementResult(rv
);
5467 NS_WARNING_ASSERTION(
5468 rv
!= NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR
,
5469 "CreateElementResult::SuggestCaretPointTo() failed, but ignored");
5470 childContent
= newListItemElementOrError
.GetNewNode();
5473 if (HTMLEditUtils::IsAnyListElement(childContent
->AsElement()) &&
5474 !childContent
->IsHTMLElement(&aNewListTag
)) {
5475 // XXX List elements shouldn't have other list elements as their
5476 // child. Why do we handle such invalid tree?
5477 // -> Maybe, for bug 525888.
5478 OwningNonNull
<Element
> listElement
= *childContent
->AsElement();
5479 CreateElementResult convertListTypeResult
=
5480 ChangeListElementType(listElement
, aNewListTag
, aNewListItemTag
);
5481 if (convertListTypeResult
.isErr()) {
5482 NS_WARNING("HTMLEditor::ChangeListElementType() failed");
5483 return convertListTypeResult
;
5485 childContent
= convertListTypeResult
.GetNewNode();
5490 if (aListElement
.IsHTMLElement(&aNewListTag
)) {
5491 return CreateElementResult(&aListElement
);
5494 CreateElementResult listElementOrError
=
5495 ReplaceContainerWithTransaction(aListElement
, aNewListTag
);
5496 if (listElementOrError
.isErr()) {
5497 NS_WARNING("HTMLEditor::ReplaceContainerWithTransaction() failed");
5498 return listElementOrError
;
5500 nsresult rv
= listElementOrError
.SuggestCaretPointTo(
5501 *this, {SuggestCaret::OnlyIfHasSuggestion
,
5502 SuggestCaret::OnlyIfTransactionsAllowedToDoIt
,
5503 SuggestCaret::AndIgnoreTrivialError
});
5504 if (NS_FAILED(rv
)) {
5505 NS_WARNING("CreateElementResult::SuggestCaretPointTo() failed");
5506 return CreateElementResult(rv
);
5508 NS_WARNING_ASSERTION(
5509 rv
!= NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR
,
5510 "CreateElementResult::SuggestCaretPointTo() failed, but ignored");
5511 return CreateElementResult(listElementOrError
.UnwrapNewNode());
5514 nsresult
HTMLEditor::CreateStyleForInsertText(
5515 const AbstractRange
& aAbstractRange
) {
5516 MOZ_ASSERT(IsEditActionDataAvailable());
5517 MOZ_ASSERT(aAbstractRange
.IsPositioned());
5518 MOZ_ASSERT(mTypeInState
);
5520 RefPtr
<Element
> documentRootElement
= GetDocument()->GetRootElement();
5521 if (NS_WARN_IF(!documentRootElement
)) {
5522 return NS_ERROR_FAILURE
;
5525 // process clearing any styles first
5526 UniquePtr
<PropItem
> item
= mTypeInState
->TakeClearProperty();
5528 EditorDOMPoint
pointToPutCaret(aAbstractRange
.StartRef());
5529 bool putCaret
= false;
5531 // Transactions may set selection, but we will set selection if necessary.
5532 AutoTransactionsConserveSelection
dontChangeMySelection(*this);
5534 while (item
&& pointToPutCaret
.GetContainer() != documentRootElement
) {
5536 ClearStyleAt(pointToPutCaret
, MOZ_KnownLive(item
->tag
),
5537 MOZ_KnownLive(item
->attr
), item
->specifiedStyle
);
5538 if (result
.Failed()) {
5539 NS_WARNING("HTMLEditor::ClearStyleAt() failed");
5542 pointToPutCaret
= result
.PointRefToCollapseSelection();
5543 item
= mTypeInState
->TakeClearProperty();
5548 // then process setting any styles
5549 int32_t relFontSize
= mTypeInState
->TakeRelativeFontSize();
5550 item
= mTypeInState
->TakeSetProperty();
5552 if (item
|| relFontSize
) {
5553 // we have at least one style to add; make a new text node to insert style
5555 if (pointToPutCaret
.IsInTextNode()) {
5556 // if we are in a text node, split it
5557 const SplitNodeResult splitTextNodeResult
= SplitNodeDeepWithTransaction(
5558 MOZ_KnownLive(*pointToPutCaret
.GetContainerAsText()), pointToPutCaret
,
5559 SplitAtEdges::eAllowToCreateEmptyContainer
);
5560 if (splitTextNodeResult
.isErr()) {
5562 "HTMLEditor::SplitNodeDeepWithTransaction(SplitAtEdges::"
5563 "eAllowToCreateEmptyContainer) failed");
5564 return splitTextNodeResult
.unwrapErr();
5566 nsresult rv
= splitTextNodeResult
.SuggestCaretPointTo(
5567 *this, {SuggestCaret::OnlyIfHasSuggestion
,
5568 SuggestCaret::OnlyIfTransactionsAllowedToDoIt
});
5569 if (NS_FAILED(rv
)) {
5570 NS_WARNING("SplitNodeResult::SuggestCaretPointTo() failed");
5573 pointToPutCaret
= splitTextNodeResult
.AtSplitPoint
<EditorDOMPoint
>();
5575 if (!pointToPutCaret
.IsInContentNode() ||
5576 !HTMLEditUtils::IsContainerNode(
5577 *pointToPutCaret
.ContainerAsContent())) {
5580 RefPtr
<Text
> newEmptyTextNode
= CreateTextNode(u
""_ns
);
5581 if (!newEmptyTextNode
) {
5582 NS_WARNING("EditorBase::CreateTextNode() failed");
5583 return NS_ERROR_FAILURE
;
5585 CreateTextResult insertNewTextNodeResult
=
5586 InsertNodeWithTransaction
<Text
>(*newEmptyTextNode
, pointToPutCaret
);
5587 if (insertNewTextNodeResult
.isErr()) {
5588 NS_WARNING("EditorBase::InsertNodeWithTransaction() failed");
5589 return insertNewTextNodeResult
.unwrapErr();
5591 insertNewTextNodeResult
.IgnoreCaretPointSuggestion();
5592 pointToPutCaret
.Set(newEmptyTextNode
, 0u);
5596 // dir indicated bigger versus smaller. 1 = bigger, -1 = smaller
5597 HTMLEditor::FontSize dir
= relFontSize
> 0 ? HTMLEditor::FontSize::incr
5598 : HTMLEditor::FontSize::decr
;
5599 for (int32_t j
= 0; j
< DeprecatedAbs(relFontSize
); j
++) {
5601 RelativeFontChangeOnTextNode(dir
, *newEmptyTextNode
, 0, UINT32_MAX
);
5602 if (NS_WARN_IF(Destroyed())) {
5603 return NS_ERROR_EDITOR_DESTROYED
;
5605 if (NS_FAILED(rv
)) {
5606 NS_WARNING("HTMLEditor::RelativeFontChangeOnTextNode() failed");
5613 nsresult rv
= SetInlinePropertyOnNode(
5614 MOZ_KnownLive(*pointToPutCaret
.GetContainerAsContent()),
5615 MOZ_KnownLive(*item
->tag
), MOZ_KnownLive(item
->attr
), item
->value
);
5616 if (NS_WARN_IF(Destroyed())) {
5617 return NS_ERROR_EDITOR_DESTROYED
;
5619 if (NS_FAILED(rv
)) {
5620 NS_WARNING("HTMLEditor::SetInlinePropertyOnNode() failed");
5623 item
= mTypeInState
->TakeSetProperty();
5631 nsresult rv
= CollapseSelectionTo(pointToPutCaret
);
5632 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
5633 "EditorBase::CollapseSelectionTo() failed");
5637 EditActionResult
HTMLEditor::AlignAsSubAction(const nsAString
& aAlignType
) {
5638 MOZ_ASSERT(IsEditActionDataAvailable());
5640 AutoPlaceholderBatch
treatAsOneTransaction(
5641 *this, ScrollSelectionIntoView::Yes
, __FUNCTION__
);
5642 IgnoredErrorResult ignoredError
;
5643 AutoEditSubActionNotifier
startToHandleEditSubAction(
5644 *this, EditSubAction::eSetOrClearAlignment
, nsIEditor::eNext
,
5646 if (NS_WARN_IF(ignoredError
.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED
))) {
5647 return EditActionResult(ignoredError
.StealNSResult());
5649 NS_WARNING_ASSERTION(
5650 !ignoredError
.Failed(),
5651 "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
5653 EditActionResult result
= CanHandleHTMLEditSubAction();
5654 if (result
.Failed() || result
.Canceled()) {
5655 NS_WARNING_ASSERTION(result
.Succeeded(),
5656 "HTMLEditor::CanHandleHTMLEditSubAction() failed");
5660 if (IsSelectionRangeContainerNotContent()) {
5661 NS_WARNING("Some selection containers are not content node, but ignored");
5662 return EditActionIgnored();
5665 nsresult rv
= EnsureNoPaddingBRElementForEmptyEditor();
5666 if (NS_WARN_IF(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
5667 return EditActionResult(NS_ERROR_EDITOR_DESTROYED
);
5669 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
5670 "EditorBase::EnsureNoPaddingBRElementForEmptyEditor() "
5671 "failed, but ignored");
5673 if (IsSelectionRangeContainerNotContent()) {
5674 NS_WARNING("Mutation event listener might have changed the selection");
5675 return EditActionHandled(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
);
5678 if (NS_SUCCEEDED(rv
) && SelectionRef().IsCollapsed()) {
5679 nsresult rv
= EnsureCaretNotAfterInvisibleBRElement();
5680 if (NS_WARN_IF(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
5681 return EditActionResult(NS_ERROR_EDITOR_DESTROYED
);
5683 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
5684 "HTMLEditor::EnsureCaretNotAfterInvisibleBRElement() "
5685 "failed, but ignored");
5686 if (NS_SUCCEEDED(rv
)) {
5687 nsresult rv
= PrepareInlineStylesForCaret();
5688 if (NS_WARN_IF(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
5689 return EditActionResult(NS_ERROR_EDITOR_DESTROYED
);
5691 NS_WARNING_ASSERTION(
5693 "HTMLEditor::PrepareInlineStylesForCaret() failed, but ignored");
5697 if (!SelectionRef().IsCollapsed()) {
5698 nsresult rv
= MaybeExtendSelectionToHardLineEdgesForBlockEditAction();
5699 if (NS_FAILED(rv
)) {
5701 "HTMLEditor::MaybeExtendSelectionToHardLineEdgesForBlockEditAction() "
5703 return EditActionResult(rv
);
5707 // AlignContentsAtSelection() creates AutoSelectionRestorer. Therefore,
5708 // we need to check whether we've been destroyed or not even if it returns
5710 rv
= AlignContentsAtSelection(aAlignType
);
5711 if (NS_WARN_IF(Destroyed())) {
5712 return EditActionHandled(NS_ERROR_EDITOR_DESTROYED
);
5714 if (NS_FAILED(rv
)) {
5715 NS_WARNING("HTMLEditor::AlignContentsAtSelection() failed");
5716 return EditActionHandled(rv
);
5719 if (IsSelectionRangeContainerNotContent()) {
5720 NS_WARNING("Mutation event listener might have changed the selection");
5721 return EditActionHandled(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
);
5724 rv
= MaybeInsertPaddingBRElementForEmptyLastLineAtSelection();
5725 NS_WARNING_ASSERTION(
5727 "HTMLEditor::MaybeInsertPaddingBRElementForEmptyLastLineAtSelection() "
5729 return EditActionHandled(rv
);
5732 nsresult
HTMLEditor::AlignContentsAtSelection(const nsAString
& aAlignType
) {
5733 MOZ_ASSERT(IsEditActionDataAvailable());
5734 MOZ_ASSERT(!IsSelectionRangeContainerNotContent());
5736 AutoSelectionRestorer
restoreSelectionLater(*this);
5738 // Convert the selection ranges into "promoted" selection ranges: This
5739 // basically just expands the range to include the immediate block parent,
5740 // and then further expands to include any ancestors whose children are all
5742 AutoTArray
<OwningNonNull
<nsIContent
>, 64> arrayOfContents
;
5743 nsresult rv
= SplitInlinesAndCollectEditTargetNodesInExtendedSelectionRanges(
5744 arrayOfContents
, EditSubAction::eSetOrClearAlignment
,
5745 CollectNonEditableNodes::Yes
);
5746 if (NS_FAILED(rv
)) {
5749 "SplitInlinesAndCollectEditTargetNodesInExtendedSelectionRanges("
5750 "eSetOrClearAlignment, CollectNonEditableNodes::Yes) failed");
5754 // If we don't have any nodes, or we have only a single br, then we are
5755 // creating an empty alignment div. We have to do some different things for
5757 bool createEmptyDivElement
= arrayOfContents
.IsEmpty();
5758 if (arrayOfContents
.Length() == 1) {
5759 OwningNonNull
<nsIContent
>& content
= arrayOfContents
[0];
5761 if (HTMLEditUtils::SupportsAlignAttr(content
)) {
5762 // The node is a table element, an hr, a paragraph, a div or a section
5763 // header; in HTML 4, it can directly carry the ALIGN attribute and we
5764 // don't need to make a div! If we are in CSS mode, all the work is done
5765 // in SetBlockElementAlign().
5767 SetBlockElementAlign(MOZ_KnownLive(*content
->AsElement()), aAlignType
,
5768 EditTarget::OnlyDescendantsExceptTable
);
5769 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
5770 "HTMLEditor::SetBlockElementAlign() failed");
5774 if (content
->IsHTMLElement(nsGkAtoms::br
)) {
5775 // The special case createEmptyDivElement code (below) that consumes
5776 // `<br>` elements can cause tables to split if the start node of the
5777 // selection is not in a table cell or caption, for example parent is a
5778 // `<tr>`. Avoid this unnecessary splitting if possible by leaving
5779 // createEmptyDivElement false so that we fall through to the normal case
5782 // XXX: It seems a little error prone for the createEmptyDivElement
5783 // special case code to assume that the start node of the selection
5784 // is the parent of the single node in the arrayOfContents, as the
5785 // paragraph above points out. Do we rely on the selection start
5786 // node because of the fact that arrayOfContents can be empty? We
5787 // should probably revisit this issue. - kin
5789 const nsRange
* firstRange
= SelectionRef().GetRangeAt(0);
5790 if (NS_WARN_IF(!firstRange
)) {
5791 return NS_ERROR_FAILURE
;
5793 const RangeBoundary
& atStartOfSelection
= firstRange
->StartRef();
5794 if (NS_WARN_IF(!atStartOfSelection
.IsSet())) {
5795 return NS_ERROR_FAILURE
;
5797 nsINode
* parent
= atStartOfSelection
.Container();
5798 createEmptyDivElement
= !HTMLEditUtils::IsAnyTableElement(parent
) ||
5799 HTMLEditUtils::IsTableCellOrCaption(*parent
);
5803 if (createEmptyDivElement
) {
5804 if (IsSelectionRangeContainerNotContent()) {
5805 NS_WARNING("Mutaiton event listener might have changed the selection");
5806 return NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
;
5808 EditActionResult result
=
5809 AlignContentsAtSelectionWithEmptyDivElement(aAlignType
);
5810 NS_WARNING_ASSERTION(
5812 "HTMLEditor::AlignContentsAtSelectionWithEmptyDivElement() failed");
5813 if (result
.Handled()) {
5814 restoreSelectionLater
.Abort();
5819 rv
= AlignNodesAndDescendants(arrayOfContents
, aAlignType
);
5820 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
5821 "HTMLEditor::AlignNodesAndDescendants() failed");
5825 EditActionResult
HTMLEditor::AlignContentsAtSelectionWithEmptyDivElement(
5826 const nsAString
& aAlignType
) {
5827 MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable());
5828 MOZ_ASSERT(!IsSelectionRangeContainerNotContent());
5830 RefPtr
<Element
> editingHost
= ComputeEditingHost();
5831 if (MOZ_UNLIKELY(NS_WARN_IF(!editingHost
))) {
5832 return EditActionResult(NS_ERROR_FAILURE
);
5835 const nsRange
* firstRange
= SelectionRef().GetRangeAt(0);
5836 if (NS_WARN_IF(!firstRange
)) {
5837 return EditActionResult(NS_ERROR_FAILURE
);
5840 EditorDOMPoint
atStartOfSelection(firstRange
->StartRef());
5841 if (NS_WARN_IF(!atStartOfSelection
.IsSet())) {
5842 return EditActionResult(NS_ERROR_FAILURE
);
5845 CreateElementResult createNewDivElementResult
=
5846 InsertElementWithSplittingAncestorsWithTransaction(
5847 *nsGkAtoms::div
, atStartOfSelection
,
5848 BRElementNextToSplitPoint::Delete
, *editingHost
);
5849 if (createNewDivElementResult
.isErr()) {
5851 "HTMLEditor::InsertElementWithSplittingAncestorsWithTransaction("
5852 "nsGkAtoms::div, BRElementNextToSplitPoint::Delete) failed");
5853 return EditActionResult(createNewDivElementResult
.unwrapErr());
5855 // We'll update selection below and nobody refers selection until then.
5856 // Therefore, we don't need to update selection here.
5857 createNewDivElementResult
.IgnoreCaretPointSuggestion();
5859 RefPtr
<Element
> newDivElement
= createNewDivElementResult
.UnwrapNewNode();
5860 MOZ_ASSERT(newDivElement
);
5861 // Remember our new block for postprocessing
5862 TopLevelEditSubActionDataRef().mNewBlockElement
= newDivElement
;
5863 // Set up the alignment on the div, using HTML or CSS
5864 nsresult rv
= SetBlockElementAlign(*newDivElement
, aAlignType
,
5865 EditTarget::OnlyDescendantsExceptTable
);
5866 if (NS_FAILED(rv
)) {
5868 "HTMLEditor::SetBlockElementAlign(EditTarget::"
5869 "OnlyDescendantsExceptTable) failed");
5870 return EditActionResult(rv
);
5872 // Put in a padding <br> element for empty last line so that it won't get
5874 CreateElementResult insertPaddingBRElementResult
=
5875 InsertPaddingBRElementForEmptyLastLineWithTransaction(
5876 EditorDOMPoint(newDivElement
, 0u));
5877 if (insertPaddingBRElementResult
.isErr()) {
5879 "HTMLEditor::InsertPaddingBRElementForEmptyLastLineWithTransaction() "
5881 return EditActionResult(insertPaddingBRElementResult
.unwrapErr());
5883 insertPaddingBRElementResult
.IgnoreCaretPointSuggestion();
5884 rv
= CollapseSelectionToStartOf(*newDivElement
);
5885 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
5886 "EditorBase::CollapseSelectionToStartOf() failed");
5887 return EditActionHandled(rv
);
5890 nsresult
HTMLEditor::AlignNodesAndDescendants(
5891 nsTArray
<OwningNonNull
<nsIContent
>>& aArrayOfContents
,
5892 const nsAString
& aAlignType
) {
5893 RefPtr
<Element
> editingHost
= ComputeEditingHost();
5894 if (MOZ_UNLIKELY(NS_WARN_IF(!editingHost
))) {
5895 return NS_ERROR_FAILURE
;
5898 // Detect all the transitions in the array, where a transition means that
5899 // adjacent nodes in the array don't have the same parent.
5900 AutoTArray
<bool, 64> transitionList
;
5901 HTMLEditor::MakeTransitionList(aArrayOfContents
, transitionList
);
5903 // Okay, now go through all the nodes and give them an align attrib or put
5904 // them in a div, or whatever is appropriate. Woohoo!
5906 RefPtr
<Element
> createdDivElement
;
5907 bool useCSS
= IsCSSEnabled();
5908 int32_t indexOfTransitionList
= -1;
5909 for (OwningNonNull
<nsIContent
>& content
: aArrayOfContents
) {
5910 ++indexOfTransitionList
;
5912 // Ignore all non-editable nodes. Leave them be.
5913 if (!EditorUtils::IsEditableContent(content
, EditorType::HTML
)) {
5917 // The node is a table element, an hr, a paragraph, a div or a section
5918 // header; in HTML 4, it can directly carry the ALIGN attribute and we
5919 // don't need to nest it, just set the alignment. In CSS, assign the
5920 // corresponding CSS styles in SetBlockElementAlign().
5921 if (HTMLEditUtils::SupportsAlignAttr(content
)) {
5923 SetBlockElementAlign(MOZ_KnownLive(*content
->AsElement()), aAlignType
,
5924 EditTarget::NodeAndDescendantsExceptTable
);
5925 if (NS_FAILED(rv
)) {
5927 "HTMLEditor::SetBlockElementAlign(EditTarget::"
5928 "NodeAndDescendantsExceptTable) failed");
5931 // Clear out createdDivElement so that we don't put nodes after this one
5933 createdDivElement
= nullptr;
5937 EditorDOMPoint
atContent(content
);
5938 if (NS_WARN_IF(!atContent
.IsSet())) {
5942 // Skip insignificant formatting text nodes to prevent unnecessary
5943 // structure splitting!
5944 if (content
->IsText() &&
5945 ((HTMLEditUtils::IsAnyTableElement(atContent
.GetContainer()) &&
5946 !HTMLEditUtils::IsTableCellOrCaption(*atContent
.GetContainer())) ||
5947 HTMLEditUtils::IsAnyListElement(atContent
.GetContainer()) ||
5948 HTMLEditUtils::IsEmptyNode(
5949 *content
, {EmptyCheckOption::TreatSingleBRElementAsVisible
}))) {
5953 // If it's a list item, or a list inside a list, forget any "current" div,
5954 // and instead put divs inside the appropriate block (td, li, etc.)
5955 if (HTMLEditUtils::IsListItem(content
) ||
5956 HTMLEditUtils::IsAnyListElement(content
)) {
5957 Element
* listOrListItemElement
= content
->AsElement();
5958 AutoEditorDOMPointOffsetInvalidator
lockChild(atContent
);
5959 // MOZ_KnownLive(*listOrListItemElement): An element of aArrayOfContents
5960 // which is array of OwningNonNull.
5961 nsresult rv
= RemoveAlignFromDescendants(
5962 MOZ_KnownLive(*listOrListItemElement
), aAlignType
,
5963 EditTarget::OnlyDescendantsExceptTable
);
5964 if (NS_FAILED(rv
)) {
5966 "HTMLEditor::RemoveAlignFromDescendants(EditTarget::"
5967 "OnlyDescendantsExceptTable) failed");
5972 if (nsStyledElement
* styledListOrListItemElement
=
5973 nsStyledElement::FromNode(listOrListItemElement
)) {
5974 // MOZ_KnownLive(*styledListOrListItemElement): An element of
5975 // aArrayOfContents which is array of OwningNonNull.
5976 Result
<int32_t, nsresult
> result
=
5977 mCSSEditUtils
->SetCSSEquivalentToHTMLStyleWithTransaction(
5978 MOZ_KnownLive(*styledListOrListItemElement
), nullptr,
5979 nsGkAtoms::align
, &aAlignType
);
5980 if (result
.isErr()) {
5981 if (result
.inspectErr() == NS_ERROR_EDITOR_DESTROYED
) {
5983 "CSSEditUtils::SetCSSEquivalentToHTMLStyleWithTransaction("
5984 "nsGkAtoms::align) destroyed the editor");
5985 return NS_ERROR_EDITOR_DESTROYED
;
5988 "CSSEditUtils::SetCSSEquivalentToHTMLStyleWithTransaction("
5989 "nsGkAtoms::align) failed, but ignored");
5992 createdDivElement
= nullptr;
5996 if (HTMLEditUtils::IsAnyListElement(atContent
.GetContainer())) {
5997 // If we don't use CSS, add a content to list element: they have to
5998 // be inside another list, i.e., >= second level of nesting.
5999 // XXX AlignContentsInAllTableCellsAndListItems() handles only list
6000 // item elements and table cells. Is it intentional? Why don't
6001 // we need to align contents in other type blocks?
6002 // MOZ_KnownLive(*listOrListItemElement): An element of aArrayOfContents
6003 // which is array of OwningNonNull.
6004 nsresult rv
= AlignContentsInAllTableCellsAndListItems(
6005 MOZ_KnownLive(*listOrListItemElement
), aAlignType
);
6006 if (NS_FAILED(rv
)) {
6008 "HTMLEditor::AlignContentsInAllTableCellsAndListItems() failed");
6011 createdDivElement
= nullptr;
6015 // Clear out createdDivElement so that we don't put nodes after this one
6019 // Need to make a div to put things in if we haven't already, or if this
6020 // node doesn't go in div we used earlier.
6021 if (!createdDivElement
|| transitionList
[indexOfTransitionList
]) {
6022 // First, check that our element can contain a div.
6023 if (!HTMLEditUtils::CanNodeContain(*atContent
.GetContainer(),
6025 // XXX Why do we return NS_OK here rather than returning error or
6030 CreateElementResult createNewDivElementResult
=
6031 InsertElementWithSplittingAncestorsWithTransaction(
6032 *nsGkAtoms::div
, atContent
, BRElementNextToSplitPoint::Keep
,
6034 if (createNewDivElementResult
.isErr()) {
6036 "HTMLEditor::InsertElementWithSplittingAncestorsWithTransaction("
6037 "nsGkAtoms::div) failed");
6038 return createNewDivElementResult
.unwrapErr();
6040 nsresult rv
= createNewDivElementResult
.SuggestCaretPointTo(
6041 *this, {SuggestCaret::OnlyIfHasSuggestion
,
6042 SuggestCaret::OnlyIfTransactionsAllowedToDoIt
});
6043 if (NS_FAILED(rv
)) {
6044 NS_WARNING("CreateElementResult::SuggestCaretPointTo() failed");
6047 RefPtr
<Element
> newDivElement
= createNewDivElementResult
.UnwrapNewNode();
6048 MOZ_ASSERT(newDivElement
);
6049 // Remember our new block for postprocessing
6050 TopLevelEditSubActionDataRef().mNewBlockElement
= newDivElement
;
6051 // Set up the alignment on the div
6052 rv
= SetBlockElementAlign(*newDivElement
, aAlignType
,
6053 EditTarget::OnlyDescendantsExceptTable
);
6054 if (NS_WARN_IF(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
6055 return NS_ERROR_EDITOR_DESTROYED
;
6057 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
6058 "HTMLEditor::SetBlockElementAlign(EditTarget::"
6059 "OnlyDescendantsExceptTable) failed, but ignored");
6060 createdDivElement
= std::move(newDivElement
);
6063 // Tuck the node into the end of the active div
6065 // MOZ_KnownLive because 'aArrayOfContents' is guaranteed to keep it alive.
6066 MoveNodeResult moveNodeResult
= MoveNodeToEndWithTransaction(
6067 MOZ_KnownLive(content
), *createdDivElement
);
6068 if (moveNodeResult
.isErr()) {
6069 NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed");
6070 return moveNodeResult
.unwrapErr();
6072 nsresult rv
= moveNodeResult
.SuggestCaretPointTo(
6073 *this, {SuggestCaret::OnlyIfHasSuggestion
,
6074 SuggestCaret::OnlyIfTransactionsAllowedToDoIt
,
6075 SuggestCaret::AndIgnoreTrivialError
});
6076 if (NS_FAILED(rv
)) {
6077 NS_WARNING("MoveNodeResult::SuggestCaretPointTo() failed");
6080 NS_WARNING_ASSERTION(
6081 rv
!= NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR
,
6082 "MoveNodeResult::SuggestCaretPointTo() failed, but ignored");
6088 nsresult
HTMLEditor::AlignContentsInAllTableCellsAndListItems(
6089 Element
& aElement
, const nsAString
& aAlignType
) {
6090 MOZ_ASSERT(IsEditActionDataAvailable());
6092 // Gather list of table cells or list items
6093 AutoTArray
<OwningNonNull
<Element
>, 64> arrayOfTableCellsAndListItems
;
6094 DOMIterator
iter(aElement
);
6095 iter
.AppendNodesToArray(
6096 +[](nsINode
& aNode
, void*) -> bool {
6097 MOZ_ASSERT(Element::FromNode(&aNode
));
6098 return HTMLEditUtils::IsTableCell(&aNode
) ||
6099 HTMLEditUtils::IsListItem(&aNode
);
6101 arrayOfTableCellsAndListItems
);
6103 // Now that we have the list, align their contents as requested
6104 for (auto& tableCellOrListItemElement
: arrayOfTableCellsAndListItems
) {
6105 // MOZ_KnownLive because 'arrayOfTableCellsAndListItems' is guaranteed to
6107 nsresult rv
= AlignBlockContentsWithDivElement(
6108 MOZ_KnownLive(tableCellOrListItemElement
), aAlignType
);
6109 if (NS_FAILED(rv
)) {
6110 NS_WARNING("HTMLEditor::AlignBlockContentsWithDivElement() failed");
6118 nsresult
HTMLEditor::AlignBlockContentsWithDivElement(
6119 Element
& aBlockElement
, const nsAString
& aAlignType
) {
6120 MOZ_ASSERT(IsEditActionDataAvailable());
6122 // XXX I don't understand why we should NOT align non-editable children
6123 // with modifying EDITABLE `<div>` element.
6124 nsCOMPtr
<nsIContent
> firstEditableContent
= HTMLEditUtils::GetFirstChild(
6125 aBlockElement
, {WalkTreeOption::IgnoreNonEditableNode
});
6126 if (!firstEditableContent
) {
6127 // This block has no editable content, nothing to align.
6131 // If there is only one editable content and it's a `<div>` element,
6132 // just set `align` attribute of it.
6133 nsCOMPtr
<nsIContent
> lastEditableContent
= HTMLEditUtils::GetLastChild(
6134 aBlockElement
, {WalkTreeOption::IgnoreNonEditableNode
});
6135 if (firstEditableContent
== lastEditableContent
&&
6136 firstEditableContent
->IsHTMLElement(nsGkAtoms::div
)) {
6137 nsresult rv
= SetAttributeOrEquivalent(
6138 MOZ_KnownLive(firstEditableContent
->AsElement()), nsGkAtoms::align
,
6140 if (NS_WARN_IF(Destroyed())) {
6141 return NS_ERROR_EDITOR_DESTROYED
;
6143 NS_WARNING_ASSERTION(
6145 "EditorBase::SetAttributeOrEquivalent(nsGkAtoms::align) failed");
6149 // Otherwise, we need to insert a `<div>` element to set `align` attribute.
6150 // XXX Don't insert the new `<div>` element until we set `align` attribute
6151 // for avoiding running mutation event listeners.
6152 CreateElementResult createNewDivElementResult
= CreateAndInsertElement(
6153 WithTransaction::Yes
, *nsGkAtoms::div
, EditorDOMPoint(&aBlockElement
, 0u),
6154 // MOZ_CAN_RUN_SCRIPT_BOUNDARY due to bug 1758868
6155 [&aAlignType
](HTMLEditor
& aHTMLEditor
, Element
& aDivElement
,
6156 const EditorDOMPoint
&) MOZ_CAN_RUN_SCRIPT_BOUNDARY
{
6157 // If aDivElement has not been connected yet, we do not need
6158 // transaction of setting align attribute here.
6159 nsresult rv
= aHTMLEditor
.SetAttributeOrEquivalent(
6160 &aDivElement
, nsGkAtoms::align
, aAlignType
,
6161 !aDivElement
.IsInComposedDoc());
6162 NS_WARNING_ASSERTION(
6164 nsPrintfCString("EditorBase::SetAttributeOrEquivalent(nsGkAtoms:: "
6165 "align, \"...\", %s) failed",
6166 !aDivElement
.IsInComposedDoc() ? "true" : "false")
6170 if (createNewDivElementResult
.isErr()) {
6172 "HTMLEditor::CreateAndInsertElement(WithTransaction::Yes, "
6173 "nsGkAtoms::div) failed");
6174 return createNewDivElementResult
.unwrapErr();
6176 nsresult rv
= createNewDivElementResult
.SuggestCaretPointTo(
6177 *this, {SuggestCaret::OnlyIfHasSuggestion
,
6178 SuggestCaret::OnlyIfTransactionsAllowedToDoIt
,
6179 SuggestCaret::AndIgnoreTrivialError
});
6180 if (NS_FAILED(rv
)) {
6181 NS_WARNING("CreateElementResult::SuggestCaretPointTo() failed");
6184 NS_WARNING_ASSERTION(
6185 rv
!= NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR
,
6186 "CreateElementResult::SuggestCaretPointTo() failed, but ignored");
6187 RefPtr
<Element
> newDivElement
= createNewDivElementResult
.UnwrapNewNode();
6188 MOZ_ASSERT(newDivElement
);
6189 // XXX This is tricky and does not work with mutation event listeners.
6190 // But I'm not sure what we should do if new content is inserted.
6191 // Anyway, I don't think that we should move editable contents
6192 // over non-editable contents. Chrome does no do that.
6193 EditorDOMPoint pointToPutCaret
;
6194 while (lastEditableContent
&& (lastEditableContent
!= newDivElement
)) {
6195 MoveNodeResult moveNodeResult
= MoveNodeWithTransaction(
6196 *lastEditableContent
, EditorDOMPoint(newDivElement
, 0u));
6197 if (moveNodeResult
.isErr()) {
6198 NS_WARNING("HTMLEditor::MoveNodeWithTransaction() failed");
6199 return moveNodeResult
.unwrapErr();
6201 moveNodeResult
.MoveCaretPointTo(
6202 pointToPutCaret
, *this,
6203 {SuggestCaret::OnlyIfHasSuggestion
,
6204 SuggestCaret::OnlyIfTransactionsAllowedToDoIt
});
6205 lastEditableContent
= HTMLEditUtils::GetLastChild(
6206 aBlockElement
, {WalkTreeOption::IgnoreNonEditableNode
});
6208 if (pointToPutCaret
.IsSet()) {
6209 nsresult rv
= CollapseSelectionTo(pointToPutCaret
);
6210 if (MOZ_UNLIKELY(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
6212 "EditorBase::CollapseSelectionTo() caused destroying the editor");
6213 return NS_ERROR_EDITOR_DESTROYED
;
6215 NS_WARNING_ASSERTION(
6217 "EditorBase::CollapseSelectionTo() failed, but ignored");
6222 size_t HTMLEditor::CollectChildren(
6223 nsINode
& aNode
, nsTArray
<OwningNonNull
<nsIContent
>>& aOutArrayOfContents
,
6224 size_t aIndexToInsertChildren
, CollectListChildren aCollectListChildren
,
6225 CollectTableChildren aCollectTableChildren
,
6226 CollectNonEditableNodes aCollectNonEditableNodes
) const {
6227 MOZ_ASSERT(IsEditActionDataAvailable());
6229 size_t numberOfFoundChildren
= 0;
6230 for (nsIContent
* content
= HTMLEditUtils::GetFirstChild(
6231 aNode
, {WalkTreeOption::IgnoreNonEditableNode
});
6232 content
; content
= content
->GetNextSibling()) {
6233 if ((aCollectListChildren
== CollectListChildren::Yes
&&
6234 (HTMLEditUtils::IsAnyListElement(content
) ||
6235 HTMLEditUtils::IsListItem(content
))) ||
6236 (aCollectTableChildren
== CollectTableChildren::Yes
&&
6237 HTMLEditUtils::IsAnyTableElement(content
))) {
6238 numberOfFoundChildren
+= CollectChildren(
6239 *content
, aOutArrayOfContents
,
6240 aIndexToInsertChildren
+ numberOfFoundChildren
, aCollectListChildren
,
6241 aCollectTableChildren
, aCollectNonEditableNodes
);
6242 } else if (aCollectNonEditableNodes
== CollectNonEditableNodes::Yes
||
6243 EditorUtils::IsEditableContent(*content
, EditorType::HTML
)) {
6244 aOutArrayOfContents
.InsertElementAt(
6245 aIndexToInsertChildren
+ numberOfFoundChildren
++, *content
);
6248 return numberOfFoundChildren
;
6251 nsresult
HTMLEditor::MaybeExtendSelectionToHardLineEdgesForBlockEditAction() {
6252 MOZ_ASSERT(IsEditActionDataAvailable());
6254 // This tweaks selections to be more "natural".
6255 // Idea here is to adjust edges of selection ranges so that they do not cross
6256 // breaks or block boundaries unless something editable beyond that boundary
6257 // is also selected. This adjustment makes it much easier for the various
6258 // block operations to determine what nodes to act on.
6260 // We don't need to mess with cell selections, and we assume multirange
6261 // selections are those.
6262 // XXX Why? Even in <input>, user can select 2 or more ranges.
6263 if (SelectionRef().RangeCount() != 1) {
6267 const RefPtr
<nsRange
> range
= SelectionRef().GetRangeAt(0);
6268 if (NS_WARN_IF(!range
)) {
6269 return NS_ERROR_FAILURE
;
6272 if (NS_WARN_IF(!range
->IsPositioned())) {
6273 return NS_ERROR_FAILURE
;
6276 const EditorDOMPoint
startPoint(range
->StartRef());
6277 if (NS_WARN_IF(!startPoint
.IsSet())) {
6278 return NS_ERROR_FAILURE
;
6280 const EditorDOMPoint
endPoint(range
->EndRef());
6281 if (NS_WARN_IF(!endPoint
.IsSet())) {
6282 return NS_ERROR_FAILURE
;
6285 // adjusted values default to original values
6286 EditorDOMPoint
newStartPoint(startPoint
);
6287 EditorDOMPoint
newEndPoint(endPoint
);
6289 // Is there any intervening visible white-space? If so we can't push
6290 // selection past that, it would visibly change meaning of users selection.
6291 WSRunScanner
wsScannerAtEnd(ComputeEditingHost(), endPoint
);
6292 WSScanResult scanResultAtEnd
=
6293 wsScannerAtEnd
.ScanPreviousVisibleNodeOrBlockBoundaryFrom(endPoint
);
6294 if (scanResultAtEnd
.Failed()) {
6296 "WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundaryFrom() failed");
6297 return NS_ERROR_FAILURE
;
6299 if (scanResultAtEnd
.ReachedSomethingNonTextContent()) {
6300 // eThisBlock and eOtherBlock conveniently distinguish cases
6301 // of going "down" into a block and "up" out of a block.
6302 if (wsScannerAtEnd
.StartsFromOtherBlockElement()) {
6303 // endpoint is just after the close of a block.
6304 nsIContent
* child
= HTMLEditUtils::GetLastLeafContent(
6305 *wsScannerAtEnd
.StartReasonOtherBlockElementPtr(),
6306 {LeafNodeType::LeafNodeOrChildBlock
});
6308 newEndPoint
.SetAfter(child
);
6310 // else block is empty - we can leave selection alone here, i think.
6311 } else if (wsScannerAtEnd
.StartsFromCurrentBlockBoundary()) {
6312 if (wsScannerAtEnd
.GetEditingHost()) {
6313 // endpoint is just after start of this block
6314 if (nsIContent
* child
= HTMLEditUtils::GetPreviousContent(
6315 endPoint
, {WalkTreeOption::IgnoreNonEditableNode
},
6316 wsScannerAtEnd
.GetEditingHost())) {
6317 newEndPoint
.SetAfter(child
);
6320 // else block is empty - we can leave selection alone here, i think.
6321 } else if (wsScannerAtEnd
.StartsFromBRElement()) {
6322 // endpoint is just after break. lets adjust it to before it.
6323 newEndPoint
.Set(wsScannerAtEnd
.StartReasonBRElementPtr());
6327 // Is there any intervening visible white-space? If so we can't push
6328 // selection past that, it would visibly change meaning of users selection.
6329 WSRunScanner
wsScannerAtStart(wsScannerAtEnd
.GetEditingHost(), startPoint
);
6330 WSScanResult scanResultAtStart
=
6331 wsScannerAtStart
.ScanNextVisibleNodeOrBlockBoundaryFrom(startPoint
);
6332 if (scanResultAtStart
.Failed()) {
6333 NS_WARNING("WSRunScanner::ScanNextVisibleNodeOrBlockBoundaryFrom() failed");
6334 return NS_ERROR_FAILURE
;
6336 if (scanResultAtStart
.ReachedSomethingNonTextContent()) {
6337 // eThisBlock and eOtherBlock conveniently distinguish cases
6338 // of going "down" into a block and "up" out of a block.
6339 if (wsScannerAtStart
.EndsByOtherBlockElement()) {
6340 // startpoint is just before the start of a block.
6341 nsINode
* child
= HTMLEditUtils::GetFirstLeafContent(
6342 *wsScannerAtStart
.EndReasonOtherBlockElementPtr(),
6343 {LeafNodeType::LeafNodeOrChildBlock
});
6345 newStartPoint
.Set(child
);
6347 // else block is empty - we can leave selection alone here, i think.
6348 } else if (wsScannerAtStart
.EndsByCurrentBlockBoundary()) {
6349 if (wsScannerAtStart
.GetEditingHost()) {
6350 // startpoint is just before end of this block
6351 if (nsIContent
* child
= HTMLEditUtils::GetNextContent(
6352 startPoint
, {WalkTreeOption::IgnoreNonEditableNode
},
6353 wsScannerAtStart
.GetEditingHost())) {
6354 newStartPoint
.Set(child
);
6357 // else block is empty - we can leave selection alone here, i think.
6358 } else if (wsScannerAtStart
.EndsByBRElement()) {
6359 // startpoint is just before a break. lets adjust it to after it.
6360 newStartPoint
.SetAfter(wsScannerAtStart
.EndReasonBRElementPtr());
6364 // There is a demented possibility we have to check for. We might have a very
6365 // strange selection that is not collapsed and yet does not contain any
6366 // editable content, and satisfies some of the above conditions that cause
6367 // tweaking. In this case we don't want to tweak the selection into a block
6368 // it was never in, etc. There are a variety of strategies one might use to
6369 // try to detect these cases, but I think the most straightforward is to see
6370 // if the adjusted locations "cross" the old values: i.e., new end before old
6371 // start, or new start after old end. If so then just leave things alone.
6373 Maybe
<int32_t> comp
= nsContentUtils::ComparePoints(
6374 startPoint
.ToRawRangeBoundary(), newEndPoint
.ToRawRangeBoundary());
6376 if (NS_WARN_IF(!comp
)) {
6377 return NS_ERROR_FAILURE
;
6381 return NS_OK
; // New end before old start.
6384 comp
= nsContentUtils::ComparePoints(newStartPoint
.ToRawRangeBoundary(),
6385 endPoint
.ToRawRangeBoundary());
6387 if (NS_WARN_IF(!comp
)) {
6388 return NS_ERROR_FAILURE
;
6392 return NS_OK
; // New start after old end.
6395 // Otherwise set selection to new values. Note that end point may be prior
6396 // to start point. So, we cannot use Selection::SetStartAndEndInLimit() here.
6398 SelectionRef().SetBaseAndExtentInLimiter(newStartPoint
, newEndPoint
, error
);
6399 if (NS_WARN_IF(Destroyed())) {
6400 error
= NS_ERROR_EDITOR_DESTROYED
;
6401 } else if (error
.Failed()) {
6402 NS_WARNING("Selection::SetBaseAndExtentInLimiter() failed");
6404 return error
.StealNSResult();
6407 template <typename EditorDOMPointType
>
6408 EditorDOMPoint
HTMLEditor::GetCurrentHardLineStartPoint(
6409 const EditorDOMPointType
& aPoint
, EditSubAction aEditSubAction
,
6410 const Element
& aEditingHost
) const {
6411 if (NS_WARN_IF(!aPoint
.IsSet())) {
6412 return EditorDOMPoint();
6415 auto point
= aPoint
.template To
<EditorDOMPoint
>();
6416 // Start scanning from the container node if aPoint is in a text node.
6417 // XXX Perhaps, IsInDataNode() must be expected.
6418 if (point
.IsInTextNode()) {
6419 if (!point
.GetContainer()->GetParentNode()) {
6420 // Okay, can't promote any further
6421 // XXX Why don't we return start of the text node?
6424 // If there is a preformatted linefeed in the text node, let's return
6425 // the point after it.
6426 EditorDOMPoint atLastPreformattedNewLine
=
6427 HTMLEditUtils::GetPreviousPreformattedNewLineInTextNode
<EditorDOMPoint
>(
6429 if (atLastPreformattedNewLine
.IsSet()) {
6430 return atLastPreformattedNewLine
.NextPoint();
6432 point
.Set(point
.GetContainer());
6435 // Look back through any further inline nodes that aren't across a <br>
6436 // from us, and that are enclosed in the same block.
6437 // I.e., looking for start of current hard line.
6438 constexpr HTMLEditUtils::WalkTreeOptions
6439 ignoreNonEditableNodeAndStopAtBlockBoundary
{
6440 WalkTreeOption::IgnoreNonEditableNode
,
6441 WalkTreeOption::StopAtBlockBoundary
};
6442 for (nsIContent
* previousEditableContent
= HTMLEditUtils::GetPreviousContent(
6443 point
, ignoreNonEditableNodeAndStopAtBlockBoundary
, &aEditingHost
);
6444 previousEditableContent
&& previousEditableContent
->GetParentNode() &&
6445 !HTMLEditUtils::IsVisibleBRElement(*previousEditableContent
) &&
6446 !HTMLEditUtils::IsBlockElement(*previousEditableContent
);
6447 previousEditableContent
= HTMLEditUtils::GetPreviousContent(
6448 point
, ignoreNonEditableNodeAndStopAtBlockBoundary
, &aEditingHost
)) {
6449 EditorDOMPoint atLastPreformattedNewLine
=
6450 HTMLEditUtils::GetPreviousPreformattedNewLineInTextNode
<EditorDOMPoint
>(
6451 EditorRawDOMPoint::AtEndOf(*previousEditableContent
));
6452 if (atLastPreformattedNewLine
.IsSet()) {
6453 return atLastPreformattedNewLine
.NextPoint();
6455 point
.Set(previousEditableContent
);
6458 // Finding the real start for this point unless current line starts after
6459 // <br> element. Look up the tree for as long as we are the first node in
6460 // the container (typically, start of nearest block ancestor), and as long
6461 // as we haven't hit the body node.
6462 for (nsIContent
* nearContent
= HTMLEditUtils::GetPreviousContent(
6463 point
, ignoreNonEditableNodeAndStopAtBlockBoundary
, &aEditingHost
);
6464 !nearContent
&& !point
.IsContainerHTMLElement(nsGkAtoms::body
) &&
6465 point
.GetContainerParent();
6466 nearContent
= HTMLEditUtils::GetPreviousContent(
6467 point
, ignoreNonEditableNodeAndStopAtBlockBoundary
, &aEditingHost
)) {
6468 // Don't keep looking up if we have found a blockquote element to act on
6469 // when we handle outdent.
6470 // XXX Sounds like this is hacky. If possible, it should be check in
6471 // outdent handler for consistency between edit sub-actions.
6472 // We should check Chromium's behavior of outdent when Selection
6473 // starts from `<blockquote>` and starts from first child of
6475 if (aEditSubAction
== EditSubAction::eOutdent
&&
6476 point
.IsContainerHTMLElement(nsGkAtoms::blockquote
)) {
6480 // Don't walk past the editable section. Note that we need to check
6481 // before walking up to a parent because we need to return the parent
6482 // object, so the parent itself might not be in the editable area, but
6483 // it's OK if we're not performing a block-level action.
6484 bool blockLevelAction
=
6485 aEditSubAction
== EditSubAction::eIndent
||
6486 aEditSubAction
== EditSubAction::eOutdent
||
6487 aEditSubAction
== EditSubAction::eSetOrClearAlignment
||
6488 aEditSubAction
== EditSubAction::eCreateOrRemoveBlock
;
6489 // XXX So, does this check whether the container is removable or not? It
6490 // seems that here can be rewritten as obviously what here tries to
6492 if (!point
.GetContainerParent()->IsInclusiveDescendantOf(&aEditingHost
) &&
6493 (blockLevelAction
||
6494 !point
.GetContainer()->IsInclusiveDescendantOf(&aEditingHost
))) {
6498 point
.Set(point
.GetContainer());
6503 template <typename EditorDOMPointType
>
6504 EditorDOMPoint
HTMLEditor::GetCurrentHardLineEndPoint(
6505 const EditorDOMPointType
& aPoint
, const Element
& aEditingHost
) const {
6506 if (NS_WARN_IF(!aPoint
.IsSet())) {
6507 return EditorDOMPoint();
6510 auto point
= aPoint
.template To
<EditorDOMPoint
>();
6511 // Start scanning from the container node if aPoint is in a text node.
6512 // XXX Perhaps, IsInDataNode() must be expected.
6513 if (point
.IsInTextNode()) {
6514 if (NS_WARN_IF(!point
.GetContainer()->GetParentNode())) {
6515 // Okay, can't promote any further
6516 // XXX Why don't we return end of the text node?
6519 EditorDOMPoint atNextPreformattedNewLine
=
6520 HTMLEditUtils::GetInclusiveNextPreformattedNewLineInTextNode
<
6521 EditorDOMPoint
>(point
);
6522 if (atNextPreformattedNewLine
.IsSet()) {
6523 // If the linefeed is last character of the text node, it may be
6524 // invisible if it's immediately before a block boundary. In such
6525 // case, we should retrun the block boundary.
6526 Element
* maybeNonEditableBlockElement
= nullptr;
6527 if (HTMLEditUtils::IsInvisiblePreformattedNewLine(
6528 atNextPreformattedNewLine
, &maybeNonEditableBlockElement
) &&
6529 maybeNonEditableBlockElement
) {
6530 // If the block is a parent of the editing host, let's return end
6532 if (maybeNonEditableBlockElement
== &aEditingHost
||
6533 !maybeNonEditableBlockElement
->IsInclusiveDescendantOf(
6535 return EditorDOMPoint::AtEndOf(*maybeNonEditableBlockElement
);
6537 // If it's invisible because of parent block boundary, return end
6538 // of the block. Otherwise, i.e., it's followed by a child block,
6539 // returns the point of the child block.
6540 if (atNextPreformattedNewLine
.ContainerAsText()
6541 ->IsInclusiveDescendantOf(maybeNonEditableBlockElement
)) {
6542 return EditorDOMPoint::AtEndOf(*maybeNonEditableBlockElement
);
6544 return EditorDOMPoint(maybeNonEditableBlockElement
);
6546 // Otherwise, return the point after the preformatted linefeed.
6547 return atNextPreformattedNewLine
.NextPoint();
6549 // want to be after the text node
6550 point
.SetAfter(point
.GetContainer());
6551 NS_WARNING_ASSERTION(point
.IsSet(), "Failed to set to after the text node");
6554 // Look ahead through any further inline nodes that aren't across a <br> from
6555 // us, and that are enclosed in the same block.
6556 // XXX Currently, we stop block-extending when finding visible <br> element.
6557 // This might be different from "block-extend" of execCommand spec.
6558 // However, the spec is really unclear.
6559 // XXX Probably, scanning only editable nodes is wrong for
6560 // EditSubAction::eCreateOrRemoveBlock because it might be better to wrap
6561 // existing inline elements even if it's non-editable. For example,
6562 // following examples with insertParagraph causes different result:
6563 // * <div contenteditable>foo[]<b contenteditable="false">bar</b></div>
6564 // * <div contenteditable>foo[]<b>bar</b></div>
6565 // * <div contenteditable>foo[]<b contenteditable="false">bar</b>baz</div>
6566 // Only in the first case, after the caret position isn't wrapped with
6567 // new <div> element.
6568 constexpr HTMLEditUtils::WalkTreeOptions
6569 ignoreNonEditableNodeAndStopAtBlockBoundary
{
6570 WalkTreeOption::IgnoreNonEditableNode
,
6571 WalkTreeOption::StopAtBlockBoundary
};
6572 for (nsIContent
* nextEditableContent
= HTMLEditUtils::GetNextContent(
6573 point
, ignoreNonEditableNodeAndStopAtBlockBoundary
, &aEditingHost
);
6574 nextEditableContent
&&
6575 !HTMLEditUtils::IsBlockElement(*nextEditableContent
) &&
6576 nextEditableContent
->GetParent();
6577 nextEditableContent
= HTMLEditUtils::GetNextContent(
6578 point
, ignoreNonEditableNodeAndStopAtBlockBoundary
, &aEditingHost
)) {
6579 EditorDOMPoint atFirstPreformattedNewLine
=
6580 HTMLEditUtils::GetInclusiveNextPreformattedNewLineInTextNode
<
6581 EditorDOMPoint
>(EditorRawDOMPoint(nextEditableContent
, 0));
6582 if (atFirstPreformattedNewLine
.IsSet()) {
6583 // If the linefeed is last character of the text node, it may be
6584 // invisible if it's immediately before a block boundary. In such
6585 // case, we should retrun the block boundary.
6586 Element
* maybeNonEditableBlockElement
= nullptr;
6587 if (HTMLEditUtils::IsInvisiblePreformattedNewLine(
6588 atFirstPreformattedNewLine
, &maybeNonEditableBlockElement
) &&
6589 maybeNonEditableBlockElement
) {
6590 // If the block is a parent of the editing host, let's return end
6592 if (maybeNonEditableBlockElement
== &aEditingHost
||
6593 !maybeNonEditableBlockElement
->IsInclusiveDescendantOf(
6595 return EditorDOMPoint::AtEndOf(*maybeNonEditableBlockElement
);
6597 // If it's invisible because of parent block boundary, return end
6598 // of the block. Otherwise, i.e., it's followed by a child block,
6599 // returns the point of the child block.
6600 if (atFirstPreformattedNewLine
.ContainerAsText()
6601 ->IsInclusiveDescendantOf(maybeNonEditableBlockElement
)) {
6602 return EditorDOMPoint::AtEndOf(*maybeNonEditableBlockElement
);
6604 return EditorDOMPoint(maybeNonEditableBlockElement
);
6606 // Otherwise, return the point after the preformatted linefeed.
6607 return atFirstPreformattedNewLine
.NextPoint();
6609 point
.SetAfter(nextEditableContent
);
6610 if (NS_WARN_IF(!point
.IsSet())) {
6613 if (HTMLEditUtils::IsVisibleBRElement(*nextEditableContent
)) {
6618 // Finding the real end for this point unless current line ends with a <br>
6619 // element. Look up the tree for as long as we are the last node in the
6620 // container (typically, block node), and as long as we haven't hit the body
6622 for (nsIContent
* nearContent
= HTMLEditUtils::GetNextContent(
6623 point
, ignoreNonEditableNodeAndStopAtBlockBoundary
, &aEditingHost
);
6624 !nearContent
&& !point
.IsContainerHTMLElement(nsGkAtoms::body
) &&
6625 point
.GetContainerParent();
6626 nearContent
= HTMLEditUtils::GetNextContent(
6627 point
, ignoreNonEditableNodeAndStopAtBlockBoundary
, &aEditingHost
)) {
6628 // Don't walk past the editable section. Note that we need to check before
6629 // walking up to a parent because we need to return the parent object, so
6630 // the parent itself might not be in the editable area, but it's OK.
6631 // XXX Maybe returning parent of editing host is really error prone since
6632 // everybody need to check whether the end point is in editing host
6633 // when they touch there.
6634 if (!point
.GetContainer()->IsInclusiveDescendantOf(&aEditingHost
) &&
6635 !point
.GetContainerParent()->IsInclusiveDescendantOf(&aEditingHost
)) {
6639 point
.SetAfter(point
.GetContainer());
6640 if (NS_WARN_IF(!point
.IsSet())) {
6647 void HTMLEditor::GetSelectionRangesExtendedToIncludeAdjuscentWhiteSpaces(
6648 nsTArray
<RefPtr
<nsRange
>>& aOutArrayOfRanges
) {
6649 MOZ_ASSERT(IsEditActionDataAvailable());
6650 MOZ_ASSERT(aOutArrayOfRanges
.IsEmpty());
6652 const uint32_t rangeCount
= SelectionRef().RangeCount();
6653 aOutArrayOfRanges
.SetCapacity(rangeCount
);
6654 for (const uint32_t i
: IntegerRange(rangeCount
)) {
6655 MOZ_ASSERT(SelectionRef().RangeCount() == rangeCount
);
6656 const nsRange
* selectionRange
= SelectionRef().GetRangeAt(i
);
6657 MOZ_ASSERT(selectionRange
);
6658 EditorRawDOMRange
rawRange(*selectionRange
);
6659 if (!rawRange
.IsPositioned() ||
6660 !rawRange
.EnsureNotInNativeAnonymousSubtree()) {
6661 continue; // ignore ranges which are in orphan fragment which were
6662 // disconnected from native anonymous subtrees
6664 RefPtr
<nsRange
> extendedRange
=
6665 CreateRangeIncludingAdjuscentWhiteSpaces(rawRange
);
6666 if (!extendedRange
) {
6667 extendedRange
= selectionRange
->CloneRange();
6670 aOutArrayOfRanges
.AppendElement(extendedRange
);
6673 void HTMLEditor::GetSelectionRangesExtendedToHardLineStartAndEnd(
6674 nsTArray
<RefPtr
<nsRange
>>& aOutArrayOfRanges
,
6675 EditSubAction aEditSubAction
) {
6676 MOZ_ASSERT(IsEditActionDataAvailable());
6677 MOZ_ASSERT(aOutArrayOfRanges
.IsEmpty());
6679 const uint32_t rangeCount
= SelectionRef().RangeCount();
6680 aOutArrayOfRanges
.SetCapacity(rangeCount
);
6681 for (const uint32_t i
: IntegerRange(rangeCount
)) {
6682 MOZ_ASSERT(SelectionRef().RangeCount() == rangeCount
);
6683 // Make a new adjusted range to represent the appropriate block content.
6684 // The basic idea is to push out the range endpoints to truly enclose the
6685 // blocks that we will affect. This call alters opRange.
6686 nsRange
* selectionRange
= SelectionRef().GetRangeAt(i
);
6687 MOZ_ASSERT(selectionRange
);
6688 EditorRawDOMRange
rawRange(*selectionRange
);
6689 if (!rawRange
.IsPositioned() ||
6690 !rawRange
.EnsureNotInNativeAnonymousSubtree()) {
6691 continue; // ignore ranges which are in orphan fragment which were
6692 // disconnected from native anonymous subtrees
6694 RefPtr
<nsRange
> extendedRange
=
6695 CreateRangeExtendedToHardLineStartAndEnd(rawRange
, aEditSubAction
);
6696 if (!extendedRange
) {
6697 extendedRange
= selectionRange
->CloneRange();
6699 aOutArrayOfRanges
.AppendElement(extendedRange
);
6703 template <typename EditorDOMPointType1
, typename EditorDOMPointType2
>
6704 void HTMLEditor::SelectBRElementIfCollapsedInEmptyBlock(
6705 EditorDOMPointType1
& aStartPoint
, EditorDOMPointType2
& aEndPoint
,
6706 const Element
& aEditingHost
) const {
6707 // MOOSE major hack:
6708 // The GetCurrentHardLineStartPoint() and GetCurrentHardLineEndPoint() don't
6709 // really do the right thing for collapsed ranges inside block elements that
6710 // contain nothing but a solo <br>. It's easier/ to put a workaround here
6711 // than to revamp them. :-(
6712 if (aStartPoint
!= aEndPoint
) {
6716 if (!aStartPoint
.IsInContentNode()) {
6720 // XXX Perhaps, this should be more careful. This may not select only one
6721 // node because this just check whether the block is empty or not,
6722 // and may not select in non-editable block. However, for inline
6723 // editing host case, it's right to look for block element without
6724 // editable state check. Now, this method is used for preparation for
6725 // other things. So, cannot write test for this method behavior.
6726 // So, perhaps, we should get rid of this method and each caller should
6727 // handle its job better.
6728 Element
* const maybeNonEditableBlockElement
=
6729 HTMLEditUtils::GetInclusiveAncestorElement(
6730 *aStartPoint
.ContainerAsContent(),
6731 HTMLEditUtils::ClosestBlockElement
);
6732 if (!maybeNonEditableBlockElement
) {
6736 // Make sure we don't go higher than our root element in the content tree
6737 if (aEditingHost
.IsInclusiveDescendantOf(maybeNonEditableBlockElement
)) {
6741 if (HTMLEditUtils::IsEmptyNode(*maybeNonEditableBlockElement
)) {
6742 aStartPoint
.Set(maybeNonEditableBlockElement
, 0u);
6743 aEndPoint
.SetToEndOf(maybeNonEditableBlockElement
);
6747 template <typename EditorDOMRangeType
>
6748 already_AddRefed
<nsRange
> HTMLEditor::CreateRangeIncludingAdjuscentWhiteSpaces(
6749 const EditorDOMRangeType
& aRange
) {
6750 MOZ_DIAGNOSTIC_ASSERT(aRange
.IsPositioned());
6751 return CreateRangeIncludingAdjuscentWhiteSpaces(aRange
.StartRef(),
6755 template <typename EditorDOMPointType1
, typename EditorDOMPointType2
>
6756 already_AddRefed
<nsRange
> HTMLEditor::CreateRangeIncludingAdjuscentWhiteSpaces(
6757 const EditorDOMPointType1
& aStartPoint
,
6758 const EditorDOMPointType2
& aEndPoint
) {
6759 MOZ_DIAGNOSTIC_ASSERT(!aStartPoint
.IsInNativeAnonymousSubtree());
6760 MOZ_DIAGNOSTIC_ASSERT(!aEndPoint
.IsInNativeAnonymousSubtree());
6762 if (!aStartPoint
.IsInContentNode() || !aEndPoint
.IsInContentNode()) {
6763 NS_WARNING_ASSERTION(aStartPoint
.IsSet(), "aStartPoint was not set");
6764 NS_WARNING_ASSERTION(aEndPoint
.IsSet(), "aEndPoint was not set");
6768 const Element
* const editingHost
= ComputeEditingHost();
6769 if (NS_WARN_IF(!editingHost
)) {
6773 auto startPoint
= aStartPoint
.template To
<EditorRawDOMPoint
>();
6774 auto endPoint
= aEndPoint
.template To
<EditorRawDOMPoint
>();
6775 SelectBRElementIfCollapsedInEmptyBlock(startPoint
, endPoint
, *editingHost
);
6777 if (NS_WARN_IF(!startPoint
.IsInContentNode()) ||
6778 NS_WARN_IF(!endPoint
.IsInContentNode())) {
6779 NS_WARNING("HTMLEditor::SelectBRElementIfCollapsedInEmptyBlock() failed");
6783 // For text actions, we want to look backwards (or forwards, as
6784 // appropriate) for additional white-space or nbsp's. We may have to act
6785 // on these later even though they are outside of the initial selection.
6786 // Even if they are in another node!
6787 // XXX Those scanners do not treat siblings of the text nodes. Perhaps,
6788 // we should use `WSRunScanner::GetFirstASCIIWhiteSpacePointCollapsedTo()`
6789 // and `WSRunScanner::GetEndOfCollapsibleASCIIWhiteSpaces()` instead.
6790 if (startPoint
.IsInTextNode()) {
6791 while (!startPoint
.IsStartOfContainer()) {
6792 if (!startPoint
.IsPreviousCharASCIISpaceOrNBSP()) {
6795 MOZ_ALWAYS_TRUE(startPoint
.RewindOffset());
6798 if (!startPoint
.GetChildOrContainerIfDataNode() ||
6799 !startPoint
.GetChildOrContainerIfDataNode()->IsInclusiveDescendantOf(
6803 if (endPoint
.IsInTextNode()) {
6804 while (!endPoint
.IsEndOfContainer()) {
6805 if (!endPoint
.IsCharASCIISpaceOrNBSP()) {
6808 MOZ_ALWAYS_TRUE(endPoint
.AdvanceOffset());
6811 EditorRawDOMPoint
lastRawPoint(endPoint
);
6812 if (!lastRawPoint
.IsStartOfContainer()) {
6813 lastRawPoint
.RewindOffset();
6815 if (!lastRawPoint
.GetChildOrContainerIfDataNode() ||
6816 !lastRawPoint
.GetChildOrContainerIfDataNode()->IsInclusiveDescendantOf(
6821 RefPtr
<nsRange
> range
=
6822 nsRange::Create(startPoint
.ToRawRangeBoundary(),
6823 endPoint
.ToRawRangeBoundary(), IgnoreErrors());
6824 NS_WARNING_ASSERTION(range
, "nsRange::Create() failed");
6825 return range
.forget();
6828 template <typename EditorDOMRangeType
>
6829 already_AddRefed
<nsRange
> HTMLEditor::CreateRangeExtendedToHardLineStartAndEnd(
6830 const EditorDOMRangeType
& aRange
, EditSubAction aEditSubAction
) const {
6831 if (!aRange
.IsPositioned()) {
6834 return CreateRangeExtendedToHardLineStartAndEnd(
6835 aRange
.StartRef(), aRange
.EndRef(), aEditSubAction
);
6838 template <typename EditorDOMPointType1
, typename EditorDOMPointType2
>
6839 already_AddRefed
<nsRange
> HTMLEditor::CreateRangeExtendedToHardLineStartAndEnd(
6840 const EditorDOMPointType1
& aStartPoint
,
6841 const EditorDOMPointType2
& aEndPoint
, EditSubAction aEditSubAction
) const {
6842 MOZ_DIAGNOSTIC_ASSERT(!aStartPoint
.IsInNativeAnonymousSubtree());
6843 MOZ_DIAGNOSTIC_ASSERT(!aEndPoint
.IsInNativeAnonymousSubtree());
6845 if (NS_WARN_IF(!aStartPoint
.IsSet()) || NS_WARN_IF(!aEndPoint
.IsSet())) {
6849 const Element
* const editingHost
= ComputeEditingHost();
6850 if (NS_WARN_IF(!editingHost
)) {
6854 auto startPoint
= aStartPoint
.template To
<EditorDOMPoint
>();
6855 auto endPoint
= aEndPoint
.template To
<EditorDOMPoint
>();
6856 SelectBRElementIfCollapsedInEmptyBlock(startPoint
, endPoint
, *editingHost
);
6858 // Make a new adjusted range to represent the appropriate block content.
6859 // This is tricky. The basic idea is to push out the range endpoints to
6860 // truly enclose the blocks that we will affect.
6862 // Make sure that the new range ends up to be in the editable section.
6863 // XXX Looks like that this check wastes the time. Perhaps, we should
6864 // implement a method which checks both two DOM points in the editor
6868 GetCurrentHardLineStartPoint(startPoint
, aEditSubAction
, *editingHost
);
6869 // XXX GetCurrentHardLineStartPoint() may return point of editing
6870 // host. Perhaps, we should change it and stop checking it here
6871 // since this check may be expensive.
6872 // XXX If the container is an element in the editing host but it points end of
6873 // the container, this returns nullptr. Is it intentional?
6874 if (!startPoint
.GetChildOrContainerIfDataNode() ||
6875 !startPoint
.GetChildOrContainerIfDataNode()->IsInclusiveDescendantOf(
6879 endPoint
= GetCurrentHardLineEndPoint(endPoint
, *editingHost
);
6880 const EditorDOMPoint lastRawPoint
=
6881 endPoint
.IsStartOfContainer() ? endPoint
: endPoint
.PreviousPoint();
6882 // XXX GetCurrentHardLineEndPoint() may return point of editing host.
6883 // Perhaps, we should change it and stop checking it here since this
6884 // check may be expensive.
6885 // XXX If the container is an element in the editing host but it points end of
6886 // the container, this returns nullptr. Is it intentional?
6887 if (!lastRawPoint
.GetChildOrContainerIfDataNode() ||
6888 !lastRawPoint
.GetChildOrContainerIfDataNode()->IsInclusiveDescendantOf(
6893 RefPtr
<nsRange
> range
=
6894 nsRange::Create(startPoint
.ToRawRangeBoundary(),
6895 endPoint
.ToRawRangeBoundary(), IgnoreErrors());
6896 NS_WARNING_ASSERTION(range
, "nsRange::Create() failed");
6897 return range
.forget();
6900 nsresult
HTMLEditor::SplitInlinesAndCollectEditTargetNodes(
6901 nsTArray
<RefPtr
<nsRange
>>& aArrayOfRanges
,
6902 nsTArray
<OwningNonNull
<nsIContent
>>& aOutArrayOfContents
,
6903 EditSubAction aEditSubAction
,
6904 CollectNonEditableNodes aCollectNonEditableNodes
) {
6905 nsresult rv
= SplitTextNodesAtRangeEnd(aArrayOfRanges
);
6906 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
6907 "HTMLEditor::SplitTextNodesAtRangeEnd() failed");
6908 if (NS_FAILED(rv
)) {
6911 rv
= SplitParentInlineElementsAtRangeEdges(aArrayOfRanges
);
6912 NS_WARNING_ASSERTION(
6914 "HTMLEditor::SplitParentInlineElementsAtRangeEdges() failed");
6915 if (NS_FAILED(rv
)) {
6918 rv
= CollectEditTargetNodes(aArrayOfRanges
, aOutArrayOfContents
,
6919 aEditSubAction
, aCollectNonEditableNodes
);
6920 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
6921 "HTMLEditor::CollectEditTargetNodes() failed");
6922 if (NS_FAILED(rv
)) {
6925 rv
= MaybeSplitElementsAtEveryBRElement(aOutArrayOfContents
, aEditSubAction
);
6926 NS_WARNING_ASSERTION(
6928 "HTMLEditor::MaybeSplitElementsAtEveryBRElement() failed");
6932 nsresult
HTMLEditor::SplitTextNodesAtRangeEnd(
6933 nsTArray
<RefPtr
<nsRange
>>& aArrayOfRanges
) {
6934 // Split text nodes. This is necessary, since given ranges may end in text
6935 // nodes in case where part of a pre-formatted elements needs to be moved.
6936 EditorDOMPoint pointToPutCaret
;
6937 nsresult rv
= NS_OK
;
6938 IgnoredErrorResult ignoredError
;
6939 for (RefPtr
<nsRange
>& range
: aArrayOfRanges
) {
6940 EditorDOMPoint
atEnd(range
->EndRef());
6941 if (NS_WARN_IF(!atEnd
.IsSet()) || !atEnd
.IsInTextNode()) {
6945 if (!atEnd
.IsStartOfContainer() && !atEnd
.IsEndOfContainer()) {
6946 // Split the text node.
6947 SplitNodeResult splitAtEndResult
= SplitNodeWithTransaction(atEnd
);
6948 if (splitAtEndResult
.isErr()) {
6949 NS_WARNING("HTMLEditor::SplitNodeWithTransaction() failed");
6950 rv
= splitAtEndResult
.unwrapErr();
6953 splitAtEndResult
.MoveCaretPointTo(
6954 pointToPutCaret
, *this,
6955 {SuggestCaret::OnlyIfTransactionsAllowedToDoIt
});
6956 MOZ_ASSERT_IF(AllowsTransactionsToChangeSelection(),
6957 pointToPutCaret
.IsSet());
6959 // Correct the range.
6960 // The new end parent becomes the parent node of the text.
6961 MOZ_ASSERT(!range
->IsInSelection());
6962 range
->SetEnd(splitAtEndResult
.AtNextContent
<EditorRawDOMPoint
>()
6963 .ToRawRangeBoundary(),
6965 NS_WARNING_ASSERTION(!ignoredError
.Failed(),
6966 "nsRange::SetEnd() failed, but ignored");
6967 ignoredError
.SuppressException();
6971 if (!pointToPutCaret
.IsSet()) {
6974 nsresult rvOfCollapseSelection
= CollapseSelectionTo(pointToPutCaret
);
6975 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvOfCollapseSelection
),
6976 "EditorBase::CollapseSelectionTo() failed");
6977 if (MOZ_UNLIKELY(rv
== NS_ERROR_EDITOR_DESTROYED
||
6978 rvOfCollapseSelection
== NS_ERROR_EDITOR_DESTROYED
)) {
6979 return NS_ERROR_EDITOR_DESTROYED
;
6981 return MOZ_LIKELY(NS_SUCCEEDED(rv
)) ? rvOfCollapseSelection
: rv
;
6984 nsresult
HTMLEditor::SplitParentInlineElementsAtRangeEdges(
6985 nsTArray
<RefPtr
<nsRange
>>& aArrayOfRanges
) {
6986 nsTArray
<OwningNonNull
<RangeItem
>> rangeItemArray
;
6987 rangeItemArray
.AppendElements(aArrayOfRanges
.Length());
6989 // First register ranges for special editor gravity
6990 for (auto& rangeItem
: rangeItemArray
) {
6991 rangeItem
= new RangeItem();
6992 rangeItem
->StoreRange(*aArrayOfRanges
[0]);
6993 RangeUpdaterRef().RegisterRangeItem(*rangeItem
);
6994 aArrayOfRanges
.RemoveElementAt(0);
6996 // Now bust up inlines.
6997 nsresult rv
= NS_OK
;
6998 for (auto& item
: Reversed(rangeItemArray
)) {
6999 // MOZ_KnownLive because 'rangeItemArray' is guaranteed to keep it alive.
7000 rv
= SplitParentInlineElementsAtRangeEdges(MOZ_KnownLive(*item
));
7001 if (NS_FAILED(rv
)) {
7002 NS_WARNING("HTMLEditor::SplitParentInlineElementsAtRangeEdges() failed");
7006 // Then unregister the ranges
7007 for (auto& item
: rangeItemArray
) {
7008 RangeUpdaterRef().DropRangeItem(item
);
7009 RefPtr
<nsRange
> range
= item
->GetRange();
7011 aArrayOfRanges
.AppendElement(range
);
7015 if (NS_WARN_IF(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
7016 return NS_ERROR_EDITOR_DESTROYED
;
7021 nsresult
HTMLEditor::CollectEditTargetNodes(
7022 nsTArray
<RefPtr
<nsRange
>>& aArrayOfRanges
,
7023 nsTArray
<OwningNonNull
<nsIContent
>>& aOutArrayOfContents
,
7024 EditSubAction aEditSubAction
,
7025 CollectNonEditableNodes aCollectNonEditableNodes
) {
7026 MOZ_ASSERT(IsEditActionDataAvailable());
7028 // Gather up a list of all the nodes
7029 for (auto& range
: aArrayOfRanges
) {
7030 DOMSubtreeIterator iter
;
7031 nsresult rv
= iter
.Init(*range
);
7032 if (NS_FAILED(rv
)) {
7033 NS_WARNING("DOMSubtreeIterator::Init() failed");
7036 if (aOutArrayOfContents
.IsEmpty()) {
7037 iter
.AppendAllNodesToArray(aOutArrayOfContents
);
7039 AutoTArray
<OwningNonNull
<nsIContent
>, 24> arrayOfTopChildren
;
7040 iter
.AppendNodesToArray(
7041 +[](nsINode
& aNode
, void* aArray
) -> bool {
7043 return !static_cast<nsTArray
<OwningNonNull
<nsIContent
>>*>(aArray
)
7046 arrayOfTopChildren
, &aOutArrayOfContents
);
7047 aOutArrayOfContents
.AppendElements(std::move(arrayOfTopChildren
));
7049 if (aCollectNonEditableNodes
== CollectNonEditableNodes::No
) {
7050 for (size_t i
= aOutArrayOfContents
.Length(); i
> 0; --i
) {
7051 if (!EditorUtils::IsEditableContent(aOutArrayOfContents
[i
- 1],
7052 EditorType::HTML
)) {
7053 aOutArrayOfContents
.RemoveElementAt(i
- 1);
7059 switch (aEditSubAction
) {
7060 case EditSubAction::eCreateOrRemoveBlock
:
7061 // Certain operations should not act on li's and td's, but rather inside
7062 // them. Alter the list as needed.
7063 for (int32_t i
= aOutArrayOfContents
.Length() - 1; i
>= 0; i
--) {
7064 OwningNonNull
<nsIContent
> content
= aOutArrayOfContents
[i
];
7065 if (HTMLEditUtils::IsListItem(content
)) {
7066 aOutArrayOfContents
.RemoveElementAt(i
);
7067 CollectChildren(*content
, aOutArrayOfContents
, i
,
7068 CollectListChildren::Yes
, CollectTableChildren::Yes
,
7069 aCollectNonEditableNodes
);
7072 // Empty text node shouldn't be selected if unnecessary
7073 for (int32_t i
= aOutArrayOfContents
.Length() - 1; i
>= 0; i
--) {
7074 if (Text
* text
= aOutArrayOfContents
[i
]->GetAsText()) {
7075 // Don't select empty text except to empty block
7076 if (!HTMLEditUtils::IsVisibleTextNode(*text
)) {
7077 aOutArrayOfContents
.RemoveElementAt(i
);
7082 case EditSubAction::eCreateOrChangeList
: {
7083 for (size_t i
= aOutArrayOfContents
.Length(); i
> 0; i
--) {
7084 // Scan for table elements. If we find table elements other than
7085 // table, replace it with a list of any editable non-table content
7086 // because if a selection range starts from end in a table-cell and
7087 // ends at or starts from outside the `<table>`, we need to make
7088 // lists in each selected table-cells.
7089 OwningNonNull
<nsIContent
> content
= aOutArrayOfContents
[i
- 1];
7090 if (HTMLEditUtils::IsAnyTableElementButNotTable(content
)) {
7091 // XXX aCollectNonEditableNodes is ignored here. Maybe a bug.
7092 aOutArrayOfContents
.RemoveElementAt(i
- 1);
7093 CollectChildren(content
, aOutArrayOfContents
, i
- 1,
7094 CollectListChildren::No
, CollectTableChildren::Yes
,
7095 CollectNonEditableNodes::Yes
);
7098 // If there is only one node in the array, and it is a `<div>`,
7099 // `<blockquote>` or a list element, then look inside of it until we
7100 // find inner list or content.
7101 if (aOutArrayOfContents
.Length() != 1) {
7104 Element
* deepestDivBlockquoteOrListElement
=
7105 HTMLEditUtils::GetInclusiveDeepestFirstChildWhichHasOneChild(
7106 aOutArrayOfContents
[0], {WalkTreeOption::IgnoreNonEditableNode
},
7107 nsGkAtoms::div
, nsGkAtoms::blockquote
, nsGkAtoms::ul
,
7108 nsGkAtoms::ol
, nsGkAtoms::dl
);
7109 if (!deepestDivBlockquoteOrListElement
) {
7112 if (deepestDivBlockquoteOrListElement
->IsAnyOfHTMLElements(
7113 nsGkAtoms::div
, nsGkAtoms::blockquote
)) {
7114 aOutArrayOfContents
.Clear();
7115 // XXX Before we're called, non-editable nodes are ignored. However,
7116 // we may append non-editable nodes here.
7117 CollectChildren(*deepestDivBlockquoteOrListElement
, aOutArrayOfContents
,
7118 0, CollectListChildren::No
, CollectTableChildren::No
,
7119 CollectNonEditableNodes::Yes
);
7122 aOutArrayOfContents
.ReplaceElementAt(
7123 0, OwningNonNull
<nsIContent
>(*deepestDivBlockquoteOrListElement
));
7126 case EditSubAction::eOutdent
:
7127 case EditSubAction::eIndent
:
7128 case EditSubAction::eSetPositionToAbsolute
:
7129 // Indent/outdent already do something special for list items, but we
7130 // still need to make sure we don't act on table elements
7131 for (int32_t i
= aOutArrayOfContents
.Length() - 1; i
>= 0; i
--) {
7132 OwningNonNull
<nsIContent
> content
= aOutArrayOfContents
[i
];
7133 if (HTMLEditUtils::IsAnyTableElementButNotTable(content
)) {
7134 aOutArrayOfContents
.RemoveElementAt(i
);
7135 CollectChildren(*content
, aOutArrayOfContents
, i
,
7136 CollectListChildren::Yes
, CollectTableChildren::Yes
,
7137 aCollectNonEditableNodes
);
7145 // Outdent should look inside of divs.
7146 if (aEditSubAction
== EditSubAction::eOutdent
&& !IsCSSEnabled()) {
7147 for (int32_t i
= aOutArrayOfContents
.Length() - 1; i
>= 0; i
--) {
7148 OwningNonNull
<nsIContent
> content
= aOutArrayOfContents
[i
];
7149 if (content
->IsHTMLElement(nsGkAtoms::div
)) {
7150 aOutArrayOfContents
.RemoveElementAt(i
);
7151 CollectChildren(*content
, aOutArrayOfContents
, i
,
7152 CollectListChildren::No
, CollectTableChildren::No
,
7153 aCollectNonEditableNodes
);
7161 nsresult
HTMLEditor::MaybeSplitElementsAtEveryBRElement(
7162 nsTArray
<OwningNonNull
<nsIContent
>>& aArrayOfContents
,
7163 EditSubAction aEditSubAction
) {
7164 // Post-process the list to break up inline containers that contain br's, but
7165 // only for operations that might care, like making lists or paragraphs
7166 switch (aEditSubAction
) {
7167 case EditSubAction::eCreateOrRemoveBlock
:
7168 case EditSubAction::eMergeBlockContents
:
7169 case EditSubAction::eCreateOrChangeList
:
7170 case EditSubAction::eSetOrClearAlignment
:
7171 case EditSubAction::eSetPositionToAbsolute
:
7172 case EditSubAction::eIndent
:
7173 case EditSubAction::eOutdent
:
7174 for (int32_t i
= aArrayOfContents
.Length() - 1; i
>= 0; i
--) {
7175 OwningNonNull
<nsIContent
>& content
= aArrayOfContents
[i
];
7176 if (HTMLEditUtils::IsInlineElement(content
) &&
7177 HTMLEditUtils::IsContainerNode(content
) && !content
->IsText()) {
7178 AutoTArray
<OwningNonNull
<nsIContent
>, 24> arrayOfInlineContents
;
7179 // MOZ_KnownLive because 'aArrayOfContents' is guaranteed to keep it
7181 nsresult rv
= SplitElementsAtEveryBRElement(MOZ_KnownLive(content
),
7182 arrayOfInlineContents
);
7183 if (NS_FAILED(rv
)) {
7184 NS_WARNING("HTMLEditor::SplitElementsAtEveryBRElement() failed");
7188 // Put these nodes in aArrayOfContents, replacing the current node
7189 aArrayOfContents
.RemoveElementAt(i
);
7190 aArrayOfContents
.InsertElementsAt(i
, arrayOfInlineContents
);
7199 Element
* HTMLEditor::GetParentListElementAtSelection() const {
7200 MOZ_ASSERT(IsEditActionDataAvailable());
7201 MOZ_ASSERT(!IsSelectionRangeContainerNotContent());
7203 const uint32_t rangeCount
= SelectionRef().RangeCount();
7204 for (const uint32_t i
: IntegerRange(rangeCount
)) {
7205 MOZ_ASSERT(SelectionRef().RangeCount() == rangeCount
);
7206 nsRange
* range
= SelectionRef().GetRangeAt(i
);
7208 for (nsINode
* parent
= range
->GetClosestCommonInclusiveAncestor(); parent
;
7209 parent
= parent
->GetParentNode()) {
7210 if (HTMLEditUtils::IsAnyListElement(parent
)) {
7211 return parent
->AsElement();
7218 nsresult
HTMLEditor::SplitParentInlineElementsAtRangeEdges(
7219 RangeItem
& aRangeItem
) {
7220 MOZ_ASSERT(IsEditActionDataAvailable());
7222 RefPtr
<Element
> editingHost
= ComputeEditingHost();
7223 if (NS_WARN_IF(!editingHost
)) {
7227 if (!aRangeItem
.Collapsed() && aRangeItem
.mEndContainer
&&
7228 aRangeItem
.mEndContainer
->IsContent()) {
7229 nsCOMPtr
<nsIContent
> mostAncestorInlineContentAtEnd
=
7230 HTMLEditUtils::GetMostDistantAncestorInlineElement(
7231 *aRangeItem
.mEndContainer
->AsContent(), editingHost
);
7233 if (mostAncestorInlineContentAtEnd
) {
7234 SplitNodeResult splitEndInlineResult
= SplitNodeDeepWithTransaction(
7235 *mostAncestorInlineContentAtEnd
, aRangeItem
.EndPoint(),
7236 SplitAtEdges::eDoNotCreateEmptyContainer
);
7237 if (splitEndInlineResult
.isErr()) {
7239 "HTMLEditor::SplitNodeDeepWithTransaction(SplitAtEdges::"
7240 "eDoNotCreateEmptyContainer) failed");
7241 return splitEndInlineResult
.unwrapErr();
7243 // Unfortunately, we need to collapse selection here for
7244 // ComputeEditingHost() since it refers selection.
7245 nsresult rv
= splitEndInlineResult
.SuggestCaretPointTo(
7246 *this, {SuggestCaret::OnlyIfHasSuggestion
,
7247 SuggestCaret::OnlyIfTransactionsAllowedToDoIt
});
7248 if (NS_FAILED(rv
)) {
7249 NS_WARNING("SplitNodeResult::SuggestCaretPointTo() failed");
7252 if (MOZ_UNLIKELY(editingHost
!= ComputeEditingHost())) {
7254 "HTMLEditor::SplitNodeDeepWithTransaction(SplitAtEdges::"
7255 "eDoNotCreateEmptyContainer) caused changing editing host");
7256 return NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
;
7258 const EditorRawDOMPoint
& splitPointAtEnd
=
7259 splitEndInlineResult
.AtSplitPoint
<EditorRawDOMPoint
>();
7260 if (MOZ_UNLIKELY(!splitPointAtEnd
.IsSet())) {
7262 "HTMLEditor::SplitNodeDeepWithTransaction(SplitAtEdges::"
7263 "eDoNotCreateEmptyContainer) didn't return split point");
7264 return NS_ERROR_FAILURE
;
7266 aRangeItem
.mEndContainer
= splitPointAtEnd
.GetContainer();
7267 aRangeItem
.mEndOffset
= splitPointAtEnd
.Offset();
7271 if (!aRangeItem
.mStartContainer
|| !aRangeItem
.mStartContainer
->IsContent()) {
7275 nsCOMPtr
<nsIContent
> mostAncestorInlineContentAtStart
=
7276 HTMLEditUtils::GetMostDistantAncestorInlineElement(
7277 *aRangeItem
.mStartContainer
->AsContent(), editingHost
);
7279 if (mostAncestorInlineContentAtStart
) {
7280 SplitNodeResult splitStartInlineResult
= SplitNodeDeepWithTransaction(
7281 *mostAncestorInlineContentAtStart
, aRangeItem
.StartPoint(),
7282 SplitAtEdges::eDoNotCreateEmptyContainer
);
7283 if (splitStartInlineResult
.isErr()) {
7285 "HTMLEditor::SplitNodeDeepWithTransaction(SplitAtEdges::"
7286 "eDoNotCreateEmptyContainer) failed");
7287 return splitStartInlineResult
.unwrapErr();
7289 // XXX Why don't we check editing host like above??
7290 nsresult rv
= splitStartInlineResult
.SuggestCaretPointTo(
7291 *this, {SuggestCaret::OnlyIfHasSuggestion
,
7292 SuggestCaret::OnlyIfTransactionsAllowedToDoIt
});
7293 if (NS_FAILED(rv
)) {
7294 NS_WARNING("SplitNodeResult::SuggestCaretPointTo() failed");
7297 // XXX If we split only here because of collapsed range, we're modifying
7298 // only start point of aRangeItem. Shouldn't we modify end point here
7299 // if it's collapsed?
7300 const EditorRawDOMPoint
& splitPointAtStart
=
7301 splitStartInlineResult
.AtSplitPoint
<EditorRawDOMPoint
>();
7302 if (MOZ_UNLIKELY(!splitPointAtStart
.IsSet())) {
7304 "HTMLEditor::SplitNodeDeepWithTransaction(SplitAtEdges::"
7305 "eDoNotCreateEmptyContainer) didn't return split point");
7306 return NS_ERROR_FAILURE
;
7308 aRangeItem
.mStartContainer
= splitPointAtStart
.GetContainer();
7309 aRangeItem
.mStartOffset
= splitPointAtStart
.Offset();
7315 nsresult
HTMLEditor::SplitElementsAtEveryBRElement(
7316 nsIContent
& aMostAncestorToBeSplit
,
7317 nsTArray
<OwningNonNull
<nsIContent
>>& aOutArrayOfContents
) {
7318 MOZ_ASSERT(IsEditActionDataAvailable());
7320 // First build up a list of all the break nodes inside the inline container.
7321 AutoTArray
<OwningNonNull
<HTMLBRElement
>, 24> arrayOfBRElements
;
7322 DOMIterator
iter(aMostAncestorToBeSplit
);
7323 iter
.AppendAllNodesToArray(arrayOfBRElements
);
7325 // If there aren't any breaks, just put inNode itself in the array
7326 if (arrayOfBRElements
.IsEmpty()) {
7327 aOutArrayOfContents
.AppendElement(aMostAncestorToBeSplit
);
7331 // Else we need to bust up aMostAncestorToBeSplit along all the breaks
7332 nsCOMPtr
<nsIContent
> nextContent
= &aMostAncestorToBeSplit
;
7333 for (OwningNonNull
<HTMLBRElement
>& brElement
: arrayOfBRElements
) {
7334 EditorDOMPoint
atBRNode(brElement
);
7335 if (NS_WARN_IF(!atBRNode
.IsSet())) {
7336 return NS_ERROR_FAILURE
;
7338 const SplitNodeResult splitNodeResult
= SplitNodeDeepWithTransaction(
7339 *nextContent
, atBRNode
, SplitAtEdges::eAllowToCreateEmptyContainer
);
7340 if (splitNodeResult
.isErr()) {
7341 NS_WARNING("HTMLEditor::SplitNodeDeepWithTransaction() failed");
7342 return splitNodeResult
.unwrapErr();
7344 nsresult rv
= splitNodeResult
.SuggestCaretPointTo(
7345 *this, {SuggestCaret::OnlyIfHasSuggestion
,
7346 SuggestCaret::OnlyIfTransactionsAllowedToDoIt
});
7347 if (NS_FAILED(rv
)) {
7348 NS_WARNING("SplitNodeResult::SuggestCaretPointTo() failed");
7351 // Put previous node at the split point.
7352 if (nsIContent
* previousContent
= splitNodeResult
.GetPreviousContent()) {
7353 // Might not be a left node. A break might have been at the very
7354 // beginning of inline container, in which case
7355 // SplitNodeDeepWithTransaction() would not actually split anything.
7356 aOutArrayOfContents
.AppendElement(*previousContent
);
7358 // When adding caret suggestion to SplitNodeResult, here didn't change
7359 // selection so that just ignore it.
7360 splitNodeResult
.IgnoreCaretPointSuggestion();
7362 // Move break outside of container and also put in node list
7363 // MOZ_KnownLive because 'arrayOfBRElements' is guaranteed to keep it alive.
7364 const MoveNodeResult moveBRElementResult
= MoveNodeWithTransaction(
7365 MOZ_KnownLive(brElement
),
7366 splitNodeResult
.AtNextContent
<EditorDOMPoint
>());
7367 if (moveBRElementResult
.isErr()) {
7368 NS_WARNING("HTMLEditor::MoveNodeWithTransaction() failed");
7369 return moveBRElementResult
.unwrapErr();
7371 rv
= moveBRElementResult
.SuggestCaretPointTo(
7372 *this, {SuggestCaret::OnlyIfHasSuggestion
,
7373 SuggestCaret::OnlyIfTransactionsAllowedToDoIt
,
7374 SuggestCaret::AndIgnoreTrivialError
});
7375 if (NS_FAILED(rv
)) {
7376 NS_WARNING("MoveNodeResult::SuggestCaretPointTo() failed");
7379 NS_WARNING_ASSERTION(
7380 rv
!= NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR
,
7381 "MoveNodeResult::SuggestCaretPointTo() failed, but ignored");
7382 aOutArrayOfContents
.AppendElement(brElement
);
7384 nextContent
= splitNodeResult
.GetNextContent();
7387 // Now tack on remaining next node.
7388 aOutArrayOfContents
.AppendElement(*nextContent
);
7394 void HTMLEditor::MakeTransitionList(
7395 const nsTArray
<OwningNonNull
<nsIContent
>>& aArrayOfContents
,
7396 nsTArray
<bool>& aTransitionArray
) {
7397 nsINode
* prevParent
= nullptr;
7398 aTransitionArray
.EnsureLengthAtLeast(aArrayOfContents
.Length());
7399 for (uint32_t i
= 0; i
< aArrayOfContents
.Length(); i
++) {
7400 aTransitionArray
[i
] = aArrayOfContents
[i
]->GetParentNode() != prevParent
;
7401 prevParent
= aArrayOfContents
[i
]->GetParentNode();
7405 Result
<EditorDOMPoint
, nsresult
>
7406 HTMLEditor::HandleInsertParagraphInHeadingElement(
7407 Element
& aHeadingElement
, const EditorDOMPoint
& aPointToSplit
) {
7408 MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable());
7410 const SplitNodeResult splitHeadingResult
=
7411 [this, &aPointToSplit
, &aHeadingElement
]() MOZ_CAN_RUN_SCRIPT
{
7412 // Get ws code to adjust any ws
7413 Result
<EditorDOMPoint
, nsresult
> preparationResult
=
7414 WhiteSpaceVisibilityKeeper::PrepareToSplitBlockElement(
7415 *this, aPointToSplit
, aHeadingElement
);
7416 if (MOZ_UNLIKELY(preparationResult
.isErr())) {
7418 "WhiteSpaceVisibilityKeeper::PrepareToSplitBlockElement() "
7420 return SplitNodeResult(preparationResult
.unwrapErr());
7422 EditorDOMPoint pointToSplit
= preparationResult
.unwrap();
7423 MOZ_ASSERT(pointToSplit
.IsInContentNode());
7426 SplitNodeResult splitResult
= SplitNodeDeepWithTransaction(
7427 aHeadingElement
, pointToSplit
,
7428 SplitAtEdges::eAllowToCreateEmptyContainer
);
7429 NS_WARNING_ASSERTION(
7431 "HTMLEditor::SplitNodeDeepWithTransaction(aHeadingElement, "
7432 "SplitAtEdges::eAllowToCreateEmptyContainer) failed");
7435 if (splitHeadingResult
.isErr()) {
7436 NS_WARNING("Failed to splitting aHeadingElement");
7437 return Err(splitHeadingResult
.unwrapErr());
7439 nsresult rv
= splitHeadingResult
.SuggestCaretPointTo(
7440 *this, {SuggestCaret::OnlyIfHasSuggestion
,
7441 SuggestCaret::OnlyIfTransactionsAllowedToDoIt
});
7442 if (NS_FAILED(rv
)) {
7443 NS_WARNING("SplitNodeResult::SuggestCaretPointTo() failed");
7446 if (MOZ_UNLIKELY(!splitHeadingResult
.DidSplit())) {
7448 "HTMLEditor::SplitNodeDeepWithTransaction(SplitAtEdges::"
7449 "eAllowToCreateEmptyContainer) didn't split aHeadingElement");
7450 return Err(NS_ERROR_FAILURE
);
7453 // If the left heading element is empty, put a padding <br> element for empty
7454 // last line into it.
7455 // FYI: leftHeadingElement is grabbed by splitHeadingResult so that it's safe
7456 // to access anytime.
7457 Element
* leftHeadingElement
=
7458 Element::FromNode(splitHeadingResult
.GetPreviousContent());
7459 MOZ_ASSERT(leftHeadingElement
,
7460 "SplitNodeResult::GetPreviousContent() should return something if "
7461 "DidSplit() returns true");
7462 MOZ_DIAGNOSTIC_ASSERT(HTMLEditUtils::IsHeader(*leftHeadingElement
));
7463 if (HTMLEditUtils::IsEmptyNode(
7464 *leftHeadingElement
,
7465 {EmptyCheckOption::TreatSingleBRElementAsVisible
})) {
7466 CreateElementResult insertPaddingBRElementResult
=
7467 InsertPaddingBRElementForEmptyLastLineWithTransaction(
7468 EditorDOMPoint(leftHeadingElement
, 0u));
7469 if (insertPaddingBRElementResult
.isErr()) {
7471 "HTMLEditor::InsertPaddingBRElementForEmptyLastLineWithTransaction("
7473 return Err(insertPaddingBRElementResult
.unwrapErr());
7475 nsresult rv
= insertPaddingBRElementResult
.SuggestCaretPointTo(
7476 *this, {SuggestCaret::OnlyIfHasSuggestion
,
7477 SuggestCaret::OnlyIfTransactionsAllowedToDoIt
,
7478 SuggestCaret::AndIgnoreTrivialError
});
7479 if (NS_FAILED(rv
)) {
7480 NS_WARNING("CreateElementResult::SuggestCaretPointTo() failed");
7483 NS_WARNING_ASSERTION(
7484 rv
!= NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR
,
7485 "CreateElementResult::SuggestCaretPointTo() failed, but ignored");
7488 // Put caret at start of the right head element if it's not empty.
7489 Element
* rightHeadingElement
=
7490 Element::FromNode(splitHeadingResult
.GetNextContent());
7491 MOZ_ASSERT(rightHeadingElement
,
7492 "SplitNodeResult::GetNextContent() should return something if "
7493 "DidSplit() returns true");
7494 if (!HTMLEditUtils::IsEmptyBlockElement(*rightHeadingElement
, {})) {
7495 return EditorDOMPoint(rightHeadingElement
, 0u);
7498 // If the right heading element is empty, delete it.
7499 // MOZ_KnownLive(rightHeadingElement) because it's grabbed by
7500 // splitHeadingResult.
7501 rv
= DeleteNodeWithTransaction(MOZ_KnownLive(*rightHeadingElement
));
7502 if (NS_FAILED(rv
)) {
7503 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
7507 // Layout tells the caret to blink in a weird place if we don't place a
7508 // break after the header.
7509 // XXX This block is dead code unless the removed right heading element is
7510 // reconnected by a mutation event listener. This is a regression of
7512 // https://searchfox.org/mozilla-central/diff/879f3317d1331818718e18776caa47be7f426a22/editor/libeditor/HTMLEditRules.cpp#6389
7513 // However, the traditional behavior is different from the other browsers.
7514 // Chrome creates new paragraph in this case. Therefore, we should just
7515 // drop this block in a follow up bug.
7516 if (rightHeadingElement
->GetNextSibling()) {
7517 // XXX Ignoring non-editable <br> element here is odd because non-editable
7518 // <br> elements also work as <br> from point of view of layout.
7519 nsIContent
* nextEditableSibling
=
7520 HTMLEditUtils::GetNextSibling(*rightHeadingElement
->GetNextSibling(),
7521 {WalkTreeOption::IgnoreNonEditableNode
});
7522 if (nextEditableSibling
&&
7523 nextEditableSibling
->IsHTMLElement(nsGkAtoms::br
)) {
7524 auto afterEditableBRElement
= EditorDOMPoint::After(*nextEditableSibling
);
7525 if (MOZ_UNLIKELY(NS_WARN_IF(!afterEditableBRElement
.IsSet()))) {
7526 return Err(NS_ERROR_FAILURE
);
7528 // Put caret at the <br> element.
7529 return afterEditableBRElement
;
7533 if (MOZ_UNLIKELY(!leftHeadingElement
->IsInComposedDoc())) {
7534 NS_WARNING("The left heading element was unexpectedly removed");
7535 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
);
7538 TopLevelEditSubActionDataRef().mCachedInlineStyles
->Clear();
7539 mTypeInState
->ClearAllProps();
7541 // Create a paragraph if the right heading element is not followed by an
7542 // editable <br> element.
7543 nsStaticAtom
& newParagraphTagName
=
7544 &DefaultParagraphSeparatorTagName() == nsGkAtoms::br
7546 : DefaultParagraphSeparatorTagName();
7547 // We want a wrapper element even if we separate with a <br>.
7548 // MOZ_KnownLive(newParagraphTagName) because it's available until shutdown.
7549 const CreateElementResult createNewParagraphElementResult
=
7550 CreateAndInsertElement(
7551 WithTransaction::Yes
, MOZ_KnownLive(newParagraphTagName
),
7552 EditorDOMPoint::After(*leftHeadingElement
),
7553 // MOZ_CAN_RUN_SCRIPT_BOUNDARY due to bug 1758868
7554 [](HTMLEditor
& aHTMLEditor
, Element
& aDivOrParagraphElement
,
7555 const EditorDOMPoint
&) MOZ_CAN_RUN_SCRIPT_BOUNDARY
{
7556 // We don't make inserting new <br> element undoable because
7557 // removing the new element from the DOM tree gets same result for
7558 // the user if aDivOrParagraphElement has not been connected yet.
7559 const auto withTransaction
=
7560 aDivOrParagraphElement
.IsInComposedDoc() ? WithTransaction::Yes
7561 : WithTransaction::No
;
7562 CreateElementResult insertBRElementResult
=
7563 aHTMLEditor
.InsertBRElement(
7565 EditorDOMPoint(&aDivOrParagraphElement
, 0u));
7566 if (insertBRElementResult
.isErr()) {
7568 nsPrintfCString("HTMLEditor::InsertBRElement(%s) failed",
7569 ToString(withTransaction
).c_str())
7571 return insertBRElementResult
.unwrapErr();
7573 // We'll update selection after inserting the new paragraph.
7574 insertBRElementResult
.IgnoreCaretPointSuggestion();
7577 if (createNewParagraphElementResult
.isErr()) {
7579 "HTMLEditor::CreateAndInsertElement(WithTransaction::Yes) failed");
7580 return Err(createNewParagraphElementResult
.unwrapErr());
7582 rv
= createNewParagraphElementResult
.SuggestCaretPointTo(
7583 *this, {SuggestCaret::OnlyIfHasSuggestion
,
7584 SuggestCaret::OnlyIfTransactionsAllowedToDoIt
,
7585 SuggestCaret::AndIgnoreTrivialError
});
7586 if (NS_FAILED(rv
)) {
7587 NS_WARNING("CreateElementResult::SuggestCaretPointTo() failed");
7590 NS_WARNING_ASSERTION(
7591 rv
!= NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR
,
7592 "CreateElementResult::SuggestCaretPointTo() failed, but ignored");
7593 MOZ_ASSERT(createNewParagraphElementResult
.GetNewNode());
7595 // Put caret at the <br> element in the following paragraph.
7596 return EditorDOMPoint(createNewParagraphElementResult
.GetNewNode(), 0u);
7599 EditActionResult
HTMLEditor::HandleInsertParagraphInParagraph(
7600 Element
& aParentDivOrP
) {
7601 MOZ_ASSERT(IsEditActionDataAvailable());
7603 nsRange
* firstRange
= SelectionRef().GetRangeAt(0);
7604 if (NS_WARN_IF(!firstRange
)) {
7605 return EditActionResult(NS_ERROR_FAILURE
);
7608 EditorDOMPoint
atStartOfSelection(firstRange
->StartRef());
7609 if (NS_WARN_IF(!atStartOfSelection
.IsSet())) {
7610 return EditActionResult(NS_ERROR_FAILURE
);
7612 MOZ_ASSERT(atStartOfSelection
.IsSetAndValid());
7614 // We shouldn't create new anchor element which has non-empty href unless
7615 // splitting middle of it because we assume that users don't want to create
7616 // *same* anchor element across two or more paragraphs in most cases.
7617 // So, adjust selection start if it's edge of anchor element(s).
7618 // XXX We don't support white-space collapsing in these cases since it needs
7619 // some additional work with WhiteSpaceVisibilityKeeper but it's not usual
7620 // case. E.g., |<a href="foo"><b>foo []</b> </a>|
7621 if (atStartOfSelection
.IsStartOfContainer()) {
7622 for (nsIContent
* container
= atStartOfSelection
.GetContainerAsContent();
7623 container
&& container
!= &aParentDivOrP
;
7624 container
= container
->GetParent()) {
7625 if (HTMLEditUtils::IsLink(container
)) {
7626 // Found link should be only in right node. So, we shouldn't split
7628 atStartOfSelection
.Set(container
);
7629 // Even if we found an anchor element, don't break because DOM API
7630 // allows to nest anchor elements.
7632 // If the container is middle of its parent, stop adjusting split point.
7633 if (container
->GetPreviousSibling()) {
7634 // XXX Should we check if previous sibling is visible content?
7635 // E.g., should we ignore comment node, invisible <br> element?
7640 // We also need to check if selection is at invisible <br> element at end
7641 // of an <a href="foo"> element because editor inserts a <br> element when
7642 // user types Enter key after a white-space which is at middle of
7643 // <a href="foo"> element and when setting selection at end of the element,
7644 // selection becomes referring the <br> element. We may need to change this
7645 // behavior later if it'd be standardized.
7646 else if (atStartOfSelection
.IsEndOfContainer() ||
7647 atStartOfSelection
.IsBRElementAtEndOfContainer()) {
7648 // If there are 2 <br> elements, the first <br> element is visible. E.g.,
7649 // |<a href="foo"><b>boo[]<br></b><br></a>|, we should split the <a>
7650 // element. Otherwise, E.g., |<a href="foo"><b>boo[]<br></b></a>|,
7651 // we should not split the <a> element and ignore inline elements in it.
7652 bool foundBRElement
= atStartOfSelection
.IsBRElementAtEndOfContainer();
7653 for (nsIContent
* container
= atStartOfSelection
.GetContainerAsContent();
7654 container
&& container
!= &aParentDivOrP
;
7655 container
= container
->GetParent()) {
7656 if (HTMLEditUtils::IsLink(container
)) {
7657 // Found link should be only in left node. So, we shouldn't split it.
7658 atStartOfSelection
.SetAfter(container
);
7659 // Even if we found an anchor element, don't break because DOM API
7660 // allows to nest anchor elements.
7662 // If the container is middle of its parent, stop adjusting split point.
7663 if (nsIContent
* nextSibling
= container
->GetNextSibling()) {
7664 if (foundBRElement
) {
7665 // If we've already found a <br> element, we assume found node is
7666 // visible <br> or something other node.
7667 // XXX Should we check if non-text data node like comment?
7671 // XXX Should we check if non-text data node like comment?
7672 if (!nextSibling
->IsHTMLElement(nsGkAtoms::br
)) {
7675 foundBRElement
= true;
7680 bool doesCRCreateNewP
= GetReturnInParagraphCreatesNewParagraph();
7681 bool splitAfterNewBR
= false;
7682 nsCOMPtr
<nsIContent
> brContent
;
7684 EditorDOMPoint
pointToSplitParentDivOrP(atStartOfSelection
);
7686 EditorDOMPoint pointToInsertBR
;
7687 if (doesCRCreateNewP
&& atStartOfSelection
.GetContainer() == &aParentDivOrP
) {
7688 // We are at the edges of the block, so, we don't need to create new <br>.
7689 brContent
= nullptr;
7690 } else if (atStartOfSelection
.IsInTextNode()) {
7691 // at beginning of text node?
7692 if (atStartOfSelection
.IsStartOfContainer()) {
7693 // is there a BR prior to it?
7694 brContent
= atStartOfSelection
.IsInContentNode()
7695 ? HTMLEditUtils::GetPreviousSibling(
7696 *atStartOfSelection
.ContainerAsContent(),
7697 {WalkTreeOption::IgnoreNonEditableNode
})
7699 if (!brContent
|| !HTMLEditUtils::IsVisibleBRElement(*brContent
) ||
7700 EditorUtils::IsPaddingBRElementForEmptyLastLine(*brContent
)) {
7701 pointToInsertBR
.Set(atStartOfSelection
.GetContainer());
7702 brContent
= nullptr;
7704 } else if (atStartOfSelection
.IsEndOfContainer()) {
7705 // we're at the end of text node...
7706 // is there a BR after to it?
7707 brContent
= atStartOfSelection
.IsInContentNode()
7708 ? HTMLEditUtils::GetNextSibling(
7709 *atStartOfSelection
.ContainerAsContent(),
7710 {WalkTreeOption::IgnoreNonEditableNode
})
7712 if (!brContent
|| !HTMLEditUtils::IsVisibleBRElement(*brContent
) ||
7713 EditorUtils::IsPaddingBRElementForEmptyLastLine(*brContent
)) {
7714 pointToInsertBR
.SetAfter(atStartOfSelection
.GetContainer());
7715 NS_WARNING_ASSERTION(
7716 pointToInsertBR
.IsSet(),
7717 "Failed to set to after the container of selection start");
7718 brContent
= nullptr;
7721 if (doesCRCreateNewP
) {
7722 // XXX We split a text node here if caret is middle of it to insert
7723 // <br> element **before** splitting aParentDivOrP. Then, if
7724 // the <br> element becomes unnecessary, it'll be removed again.
7725 // So this does much more complicated things than what we want to
7726 // do here. We should handle this case separately to make the code
7728 Result
<EditorDOMPoint
, nsresult
> pointToSplitOrError
=
7729 WhiteSpaceVisibilityKeeper::PrepareToSplitBlockElement(
7730 *this, pointToSplitParentDivOrP
, aParentDivOrP
);
7731 if (NS_WARN_IF(Destroyed())) {
7732 return EditActionResult(NS_ERROR_EDITOR_DESTROYED
);
7734 if (MOZ_UNLIKELY(pointToSplitOrError
.isErr())) {
7736 "WhiteSpaceVisibilityKeeper::PrepareToSplitBlockElement() "
7738 return EditActionResult(pointToSplitOrError
.unwrapErr());
7740 MOZ_ASSERT(pointToSplitOrError
.inspect().IsSetAndValid());
7741 if (pointToSplitOrError
.inspect().IsSet()) {
7742 pointToSplitParentDivOrP
= pointToSplitOrError
.unwrap();
7744 const SplitNodeResult splitParentDivOrPResult
=
7745 SplitNodeWithTransaction(pointToSplitParentDivOrP
);
7746 if (splitParentDivOrPResult
.isErr()) {
7747 NS_WARNING("HTMLEditor::SplitNodeWithTransaction() failed");
7748 return EditActionResult(splitParentDivOrPResult
.unwrapErr());
7750 nsresult rv
= splitParentDivOrPResult
.SuggestCaretPointTo(
7751 *this, {SuggestCaret::OnlyIfTransactionsAllowedToDoIt
});
7752 if (NS_FAILED(rv
)) {
7753 NS_WARNING("SplitNodeResult::SuggestCaretPointTo() failed");
7754 return EditActionHandled(rv
);
7756 pointToSplitParentDivOrP
.SetToEndOf(
7757 splitParentDivOrPResult
.GetPreviousContent());
7760 // We need to put new <br> after the left node if given node was split
7762 pointToInsertBR
.SetAfter(pointToSplitParentDivOrP
.GetContainer());
7765 // not in a text node.
7766 // is there a BR prior to it?
7767 Element
* editingHost
= ComputeEditingHost();
7768 nsIContent
* nearContent
=
7769 editingHost
? HTMLEditUtils::GetPreviousContent(
7771 {WalkTreeOption::IgnoreNonEditableNode
}, editingHost
)
7773 if (!nearContent
|| !HTMLEditUtils::IsVisibleBRElement(*nearContent
) ||
7774 EditorUtils::IsPaddingBRElementForEmptyLastLine(*nearContent
)) {
7775 // is there a BR after it?
7776 nearContent
= editingHost
? HTMLEditUtils::GetNextContent(
7778 {WalkTreeOption::IgnoreNonEditableNode
},
7781 if (!nearContent
|| !HTMLEditUtils::IsVisibleBRElement(*nearContent
) ||
7782 EditorUtils::IsPaddingBRElementForEmptyLastLine(*nearContent
)) {
7783 pointToInsertBR
= atStartOfSelection
;
7784 splitAfterNewBR
= true;
7787 if (!pointToInsertBR
.IsSet() && nearContent
->IsHTMLElement(nsGkAtoms::br
)) {
7788 brContent
= nearContent
;
7791 if (pointToInsertBR
.IsSet()) {
7792 // if CR does not create a new P, default to BR creation
7793 if (NS_WARN_IF(!doesCRCreateNewP
)) {
7794 return EditActionResult(NS_OK
);
7797 CreateElementResult insertBRElementResult
=
7798 InsertBRElement(WithTransaction::Yes
, pointToInsertBR
);
7799 if (insertBRElementResult
.isErr()) {
7800 NS_WARNING("HTMLEditor::InsertBRElement(WithTransaction::Yes) failed");
7801 return EditActionResult(insertBRElementResult
.unwrapErr());
7803 nsresult rv
= insertBRElementResult
.SuggestCaretPointTo(
7804 *this, {SuggestCaret::OnlyIfHasSuggestion
,
7805 SuggestCaret::OnlyIfTransactionsAllowedToDoIt
,
7806 SuggestCaret::AndIgnoreTrivialError
});
7807 if (NS_FAILED(rv
)) {
7808 NS_WARNING("CreateElementResult::SuggestCaretPointTo() failed");
7809 return EditActionResult(rv
);
7811 NS_WARNING_ASSERTION(
7812 rv
!= NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR
,
7813 "CreateElementResult::SuggestCaretPointTo() failed, but ignored");
7814 MOZ_ASSERT(insertBRElementResult
.GetNewNode());
7815 if (splitAfterNewBR
) {
7816 // We split the parent after the br we've just inserted.
7817 pointToSplitParentDivOrP
.SetAfter(insertBRElementResult
.GetNewNode());
7818 NS_WARNING_ASSERTION(pointToSplitParentDivOrP
.IsSet(),
7819 "Failed to set after the new <br>");
7821 brContent
= insertBRElementResult
.UnwrapNewNode();
7823 EditActionResult
result(
7824 SplitParagraph(aParentDivOrP
, pointToSplitParentDivOrP
, brContent
));
7825 NS_WARNING_ASSERTION(result
.Succeeded(),
7826 "HTMLEditor::SplitParagraph() failed");
7827 result
.MarkAsHandled();
7831 nsresult
HTMLEditor::SplitParagraph(Element
& aParentDivOrP
,
7832 const EditorDOMPoint
& aStartOfRightNode
,
7833 nsIContent
* aNextBRNode
) {
7834 MOZ_ASSERT(IsEditActionDataAvailable());
7836 Result
<EditorDOMPoint
, nsresult
> preparationResult
=
7837 WhiteSpaceVisibilityKeeper::PrepareToSplitBlockElement(
7838 *this, aStartOfRightNode
, aParentDivOrP
);
7839 if (MOZ_UNLIKELY(preparationResult
.isErr())) {
7841 "WhiteSpaceVisibilityKeeper::PrepareToSplitBlockElement() failed");
7842 return preparationResult
.unwrapErr();
7844 EditorDOMPoint pointToSplit
= preparationResult
.unwrap();
7845 MOZ_ASSERT(pointToSplit
.IsInContentNode());
7847 // Split the paragraph.
7848 const SplitNodeResult splitDivOrPResult
= SplitNodeDeepWithTransaction(
7849 aParentDivOrP
, pointToSplit
, SplitAtEdges::eAllowToCreateEmptyContainer
);
7850 if (splitDivOrPResult
.isErr()) {
7851 NS_WARNING("HTMLEditor::SplitNodeDeepWithTransaction() failed");
7852 return splitDivOrPResult
.unwrapErr();
7854 nsresult rv
= splitDivOrPResult
.SuggestCaretPointTo(
7855 *this, {SuggestCaret::OnlyIfHasSuggestion
,
7856 SuggestCaret::OnlyIfTransactionsAllowedToDoIt
});
7857 if (NS_FAILED(rv
)) {
7858 NS_WARNING("SplitNodeResult::SuggestCaretPointTo() failed");
7861 if (MOZ_UNLIKELY(!splitDivOrPResult
.DidSplit())) {
7863 "HTMLEditor::SplitNodeDeepWithTransaction() didn't split any nodes");
7864 return NS_ERROR_FAILURE
;
7867 Element
* leftDivOrParagraphElement
=
7868 Element::FromNode(splitDivOrPResult
.GetPreviousContent());
7869 MOZ_ASSERT(leftDivOrParagraphElement
,
7870 "SplitNodeResult::GetPreviousContent() should return something if "
7871 "DidSplit() returns true");
7872 Element
* rightDivOrParagraphElement
=
7873 Element::FromNode(splitDivOrPResult
.GetNextContent());
7874 MOZ_ASSERT(rightDivOrParagraphElement
,
7875 "SplitNodeResult::GetNextContent() should return something if "
7876 "DidSplit() returns true");
7878 // Get rid of the break, if it is visible (otherwise it may be needed to
7879 // prevent an empty p).
7880 if (aNextBRNode
&& HTMLEditUtils::IsVisibleBRElement(*aNextBRNode
)) {
7881 nsresult rv
= DeleteNodeWithTransaction(*aNextBRNode
);
7882 if (NS_FAILED(rv
)) {
7883 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
7888 // Remove ID attribute on the paragraph from the right node.
7889 // MOZ_KnownLive(rightDivOrParagraphElement) because it's grabbed by
7890 // splitDivOrPResult.
7891 rv
= RemoveAttributeWithTransaction(
7892 MOZ_KnownLive(*rightDivOrParagraphElement
), *nsGkAtoms::id
);
7893 if (NS_WARN_IF(Destroyed())) {
7894 return NS_ERROR_EDITOR_DESTROYED
;
7896 if (NS_FAILED(rv
)) {
7898 "EditorBase::RemoveAttributeWithTransaction(nsGkAtoms::id) failed");
7902 // We need to ensure to both paragraphs visible even if they are empty.
7903 // However, padding <br> element for empty last line isn't useful in this
7904 // case because it'll be ignored by PlaintextSerializer. Additionally,
7905 // it'll be exposed as <br> with Element.innerHTML. Therefore, we can use
7906 // normal <br> elements for placeholder in this case. Note that Chromium
7908 // MOZ_KnownLive(leftDivOrParagraphElement) because it's grabbed by
7909 // splitDivOrResult.
7910 rv
= InsertBRElementIfEmptyBlockElement(
7911 MOZ_KnownLive(*leftDivOrParagraphElement
));
7912 if (NS_FAILED(rv
)) {
7913 NS_WARNING("HTMLEditor::InsertBRElementIfEmptyBlockElement() failed");
7916 // MOZ_KnownLive(rightDivOrParagraphElement) because it's grabbed by
7917 // splitDivOrResult.
7918 rv
= InsertBRElementIfEmptyBlockElement(
7919 MOZ_KnownLive(*rightDivOrParagraphElement
));
7920 if (NS_FAILED(rv
)) {
7921 NS_WARNING("HTMLEditor::InsertBRElementIfEmptyBlockElement() failed");
7925 // selection to beginning of right hand para;
7926 // look inside any containers that are up front.
7927 nsCOMPtr
<nsIContent
> child
= HTMLEditUtils::GetFirstLeafContent(
7928 *rightDivOrParagraphElement
, {LeafNodeType::LeafNodeOrChildBlock
});
7929 if (child
&& (child
->IsText() || HTMLEditUtils::IsContainerNode(*child
))) {
7930 nsresult rv
= CollapseSelectionToStartOf(*child
);
7931 if (MOZ_UNLIKELY(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
7933 "EditorBase::CollapseSelectionTo() caused destroying the editor");
7934 return NS_ERROR_EDITOR_DESTROYED
;
7936 NS_WARNING_ASSERTION(
7938 "EditorBase::CollapseSelectionToStartOf() failed, but ignored");
7940 nsresult rv
= CollapseSelectionTo(EditorRawDOMPoint(child
));
7941 if (MOZ_UNLIKELY(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
7943 "EditorBase::CollapseSelectionTo() caused destroying the editor");
7944 return NS_ERROR_EDITOR_DESTROYED
;
7946 NS_WARNING_ASSERTION(
7948 "EditorBase::CollapseSelectionTo() failed, but ignored");
7953 Result
<EditorDOMPoint
, nsresult
>
7954 HTMLEditor::HandleInsertParagraphInListItemElement(
7955 Element
& aListItemElement
, const EditorDOMPoint
& aPointToSplit
,
7956 Element
& aEditingHost
) {
7957 MOZ_ASSERT(IsEditActionDataAvailable());
7958 MOZ_ASSERT(HTMLEditUtils::IsListItem(&aListItemElement
));
7960 // If aListItemElement is empty, then we want to outdent its content.
7961 if (&aEditingHost
!= aListItemElement
.GetParentElement() &&
7962 HTMLEditUtils::IsEmptyBlockElement(aListItemElement
, {})) {
7963 RefPtr
<Element
> leftListElement
= aListItemElement
.GetParentElement();
7964 // If the given list item element is not the last list item element of
7965 // its parent nor not followed by sub list elements, split the parent
7967 if (!HTMLEditUtils::IsLastChild(aListItemElement
,
7968 {WalkTreeOption::IgnoreNonEditableNode
})) {
7969 const SplitNodeResult splitListItemParentResult
=
7970 SplitNodeWithTransaction(EditorDOMPoint(&aListItemElement
));
7971 if (splitListItemParentResult
.isErr()) {
7972 NS_WARNING("HTMLEditor::SplitNodeWithTransaction() failed");
7973 return Err(splitListItemParentResult
.unwrapErr());
7975 if (MOZ_UNLIKELY(!splitListItemParentResult
.DidSplit())) {
7977 "HTMLEditor::SplitNodeWithTransaction() didn't split the parent of "
7978 "aListItemElement");
7979 MOZ_ASSERT(!splitListItemParentResult
.HasCaretPointSuggestion());
7980 return Err(NS_ERROR_FAILURE
);
7982 nsresult rv
= splitListItemParentResult
.SuggestCaretPointTo(
7983 *this, {SuggestCaret::OnlyIfTransactionsAllowedToDoIt
});
7984 if (NS_FAILED(rv
)) {
7985 NS_WARNING("SplitNodeResult::SuggestCaretPointTo() failed");
7989 Element::FromNode(splitListItemParentResult
.GetPreviousContent());
7990 MOZ_DIAGNOSTIC_ASSERT(leftListElement
);
7993 auto afterLeftListElement
= EditorDOMPoint::After(leftListElement
);
7994 if (MOZ_UNLIKELY(!afterLeftListElement
.IsSet())) {
7995 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
);
7998 // If aListItemElement is in an invalid sub-list element, move it into
7999 // the grand parent list element in order to outdent.
8000 if (HTMLEditUtils::IsAnyListElement(afterLeftListElement
.GetContainer())) {
8001 const MoveNodeResult moveListItemElementResult
=
8002 MoveNodeWithTransaction(aListItemElement
, afterLeftListElement
);
8003 if (moveListItemElementResult
.isErr()) {
8004 NS_WARNING("HTMLEditor::MoveNodeWithTransaction() failed");
8005 return Err(moveListItemElementResult
.unwrapErr());
8007 moveListItemElementResult
.IgnoreCaretPointSuggestion();
8008 return EditorDOMPoint(&aListItemElement
, 0u);
8011 // Otherwise, replace the empty aListItemElement with a new paragraph.
8012 nsresult rv
= DeleteNodeWithTransaction(aListItemElement
);
8013 if (NS_FAILED(rv
)) {
8014 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
8017 nsStaticAtom
& newParagraphTagName
=
8018 &DefaultParagraphSeparatorTagName() == nsGkAtoms::br
8020 : DefaultParagraphSeparatorTagName();
8021 // MOZ_KnownLive(newParagraphTagName) because it's available until shutdown.
8022 const CreateElementResult createNewParagraphElementResult
=
8023 CreateAndInsertElement(
8024 WithTransaction::Yes
, MOZ_KnownLive(newParagraphTagName
),
8025 afterLeftListElement
,
8026 // MOZ_CAN_RUN_SCRIPT_BOUNDARY due to bug 1758868
8027 [](HTMLEditor
& aHTMLEditor
, Element
& aDivOrParagraphElement
,
8028 const EditorDOMPoint
&) MOZ_CAN_RUN_SCRIPT_BOUNDARY
{
8029 // We don't make inserting new <br> element undoable because
8030 // removing the new element from the DOM tree gets same result
8031 // for the user if aDivOrParagraphElement has not been
8033 const auto withTransaction
=
8034 aDivOrParagraphElement
.IsInComposedDoc()
8035 ? WithTransaction::Yes
8036 : WithTransaction::No
;
8037 CreateElementResult insertBRElementResult
=
8038 aHTMLEditor
.InsertBRElement(
8040 EditorDOMPoint(&aDivOrParagraphElement
, 0u));
8041 if (insertBRElementResult
.isErr()) {
8043 nsPrintfCString("HTMLEditor::InsertBRElement(%s) failed",
8044 ToString(withTransaction
).c_str())
8046 return insertBRElementResult
.unwrapErr();
8048 // We'll update selection after inserting the paragraph.
8049 insertBRElementResult
.IgnoreCaretPointSuggestion();
8052 if (createNewParagraphElementResult
.isErr()) {
8054 "HTMLEditor::CreateAndInsertElement(WithTransaction::Yes) failed");
8055 return Err(createNewParagraphElementResult
.unwrapErr());
8057 rv
= createNewParagraphElementResult
.SuggestCaretPointTo(
8058 *this, {SuggestCaret::OnlyIfHasSuggestion
,
8059 SuggestCaret::OnlyIfTransactionsAllowedToDoIt
,
8060 SuggestCaret::AndIgnoreTrivialError
});
8061 if (NS_FAILED(rv
)) {
8062 NS_WARNING("CreateElementResult::SuggestCaretPointTo() failed");
8065 NS_WARNING_ASSERTION(
8066 rv
!= NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR
,
8067 "CreateElementResult::SuggestCaretPointTo() failed, but ignored");
8068 MOZ_ASSERT(createNewParagraphElementResult
.GetNewNode());
8069 return EditorDOMPoint(createNewParagraphElementResult
.GetNewNode(), 0u);
8072 // If aListItemElement has some content or aListItemElement is empty but it's
8073 // a child of editing host, we want a new list item at the same list level.
8074 // First, sort out white-spaces.
8075 Result
<EditorDOMPoint
, nsresult
> preparationResult
=
8076 WhiteSpaceVisibilityKeeper::PrepareToSplitBlockElement(
8077 *this, aPointToSplit
, aListItemElement
);
8078 if (preparationResult
.isErr()) {
8080 "WhiteSpaceVisibilityKeeper::PrepareToSplitBlockElement() failed");
8081 return Err(preparationResult
.unwrapErr());
8083 EditorDOMPoint pointToSplit
= preparationResult
.unwrap();
8084 MOZ_ASSERT(pointToSplit
.IsInContentNode());
8086 // Now split the list item.
8087 const SplitNodeResult splitListItemResult
=
8088 SplitNodeDeepWithTransaction(aListItemElement
, pointToSplit
,
8089 SplitAtEdges::eAllowToCreateEmptyContainer
);
8090 if (splitListItemResult
.isErr()) {
8091 NS_WARNING("HTMLEditor::SplitNodeDeepWithTransaction() failed");
8092 return Err(splitListItemResult
.unwrapErr());
8094 nsresult rv
= splitListItemResult
.SuggestCaretPointTo(
8095 *this, {SuggestCaret::OnlyIfHasSuggestion
,
8096 SuggestCaret::OnlyIfTransactionsAllowedToDoIt
});
8097 if (NS_FAILED(rv
)) {
8098 NS_WARNING("SplitNodeResult::SuggestCaretPointTo() failed");
8102 if (MOZ_UNLIKELY(!aListItemElement
.GetParent())) {
8103 NS_WARNING("Somebody disconnected the target listitem from the parent");
8104 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
);
8107 // If aListItemElement is not replaced, we should not do anything anymore.
8108 if (MOZ_UNLIKELY(!splitListItemResult
.DidSplit()) ||
8109 NS_WARN_IF(!splitListItemResult
.GetNewContent()->IsElement()) ||
8110 NS_WARN_IF(!splitListItemResult
.GetOriginalContent()->IsElement())) {
8111 NS_WARNING("HTMLEditor::SplitNodeDeepWithTransaction() didn't split");
8112 return Err(NS_ERROR_FAILURE
);
8115 // FYI: They are grabbed by splitListItemResult so that they are known live
8117 Element
& leftListItemElement
=
8118 *splitListItemResult
.GetPreviousContent()->AsElement();
8119 Element
& rightListItemElement
=
8120 *splitListItemResult
.GetNextContent()->AsElement();
8122 // Hack: until I can change the damaged doc range code back to being
8123 // extra-inclusive, I have to manually detect certain list items that may be
8125 if (HTMLEditUtils::IsEmptyNode(
8126 leftListItemElement
,
8127 {EmptyCheckOption::TreatSingleBRElementAsVisible
})) {
8128 CreateElementResult insertPaddingBRElementResult
=
8129 InsertPaddingBRElementForEmptyLastLineWithTransaction(
8130 EditorDOMPoint(&leftListItemElement
, 0u));
8131 if (insertPaddingBRElementResult
.isErr()) {
8133 "HTMLEditor::InsertPaddingBRElementForEmptyLastLineWithTransaction("
8135 return Err(insertPaddingBRElementResult
.unwrapErr());
8137 // We're returing a candidate point to put caret so that we don't need to
8139 insertPaddingBRElementResult
.IgnoreCaretPointSuggestion();
8140 return EditorDOMPoint(&rightListItemElement
, 0u);
8143 if (HTMLEditUtils::IsEmptyNode(rightListItemElement
)) {
8144 // If aListItemElement is a <dd> or a <dt> and the right list item is empty
8145 // or a direct child of the editing host, replace it a new list item element
8146 // whose type is the other one.
8147 if (aListItemElement
.IsAnyOfHTMLElements(nsGkAtoms::dd
, nsGkAtoms::dt
)) {
8148 nsStaticAtom
& nextDefinitionListItemTagName
=
8149 aListItemElement
.IsHTMLElement(nsGkAtoms::dt
) ? *nsGkAtoms::dd
8151 // MOZ_KnownLive(nextDefinitionListItemTagName) because it's available
8153 CreateElementResult createNewListItemElementResult
=
8154 CreateAndInsertElement(WithTransaction::Yes
,
8155 MOZ_KnownLive(nextDefinitionListItemTagName
),
8156 EditorDOMPoint::After(rightListItemElement
));
8157 if (createNewListItemElementResult
.isErr()) {
8159 "HTMLEditor::CreateAndInsertElement(WithTransaction::Yes) failed");
8160 return Err(createNewListItemElementResult
.unwrapErr());
8162 nsresult rv
= createNewListItemElementResult
.SuggestCaretPointTo(
8163 *this, {SuggestCaret::OnlyIfHasSuggestion
,
8164 SuggestCaret::OnlyIfTransactionsAllowedToDoIt
,
8165 SuggestCaret::AndIgnoreTrivialError
});
8166 if (NS_FAILED(rv
)) {
8167 NS_WARNING("CreateElementResult::SuggestCaretPointTo() failed");
8170 NS_WARNING_ASSERTION(
8171 rv
!= NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR
,
8172 "CreateElementResult::SuggestCaretPointTo() failed, but ignored");
8173 RefPtr
<Element
> newListItemElement
=
8174 createNewListItemElementResult
.UnwrapNewNode();
8175 MOZ_ASSERT(newListItemElement
);
8176 // MOZ_KnownLive(rightListItemElement) because it's grabbed by
8177 // splitListItemResult.
8178 rv
= DeleteNodeWithTransaction(MOZ_KnownLive(rightListItemElement
));
8179 if (NS_FAILED(rv
)) {
8180 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
8183 return EditorDOMPoint(newListItemElement
, 0u);
8186 // If aListItemElement is a <li> and the right list item becomes empty or a
8187 // direct child of the editing host, copy all inline elements affecting to
8188 // the style at end of the left list item element to the right list item
8190 // MOZ_KnownLive(*ListItemElement) because they are grabbed by
8191 // splitListItemResult.
8192 Result
<EditorDOMPoint
, nsresult
> pointToPutCaretOrError
=
8193 CopyLastEditableChildStylesWithTransaction(
8194 MOZ_KnownLive(leftListItemElement
),
8195 MOZ_KnownLive(rightListItemElement
), aEditingHost
);
8196 if (MOZ_UNLIKELY(pointToPutCaretOrError
.isErr())) {
8198 "HTMLEditor::CopyLastEditableChildStylesWithTransaction() failed");
8199 return Err(pointToPutCaretOrError
.unwrapErr());
8201 return pointToPutCaretOrError
.unwrap();
8204 // If the right list item element is not empty, we need to consider where to
8205 // put caret in it. If it has non-container inline elements, <br> or <hr>, at
8206 // the element is proper position.
8207 WSScanResult forwardScanFromStartOfListItemResult
=
8208 WSRunScanner::ScanNextVisibleNodeOrBlockBoundary(
8209 &aEditingHost
, EditorRawDOMPoint(&rightListItemElement
, 0u));
8210 if (MOZ_UNLIKELY(forwardScanFromStartOfListItemResult
.Failed())) {
8211 NS_WARNING("WSRunScanner::ScanNextVisibleNodeOrBlockBoundary() failed");
8212 return Err(NS_ERROR_FAILURE
);
8214 if (forwardScanFromStartOfListItemResult
.ReachedSpecialContent() ||
8215 forwardScanFromStartOfListItemResult
.ReachedBRElement() ||
8216 forwardScanFromStartOfListItemResult
.ReachedHRElement()) {
8217 auto atFoundElement
=
8218 forwardScanFromStartOfListItemResult
.PointAtContent
<EditorDOMPoint
>();
8219 if (MOZ_UNLIKELY(NS_WARN_IF(!atFoundElement
.IsSetAndValid()))) {
8220 return Err(NS_ERROR_FAILURE
);
8222 return atFoundElement
;
8225 // Otherwise, return the point at first visible thing.
8226 // XXX This may be not meaningful position if it reached block element
8227 // in aListItemElement.
8228 return forwardScanFromStartOfListItemResult
.Point
<EditorDOMPoint
>();
8231 nsresult
HTMLEditor::MoveNodesIntoNewBlockquoteElement(
8232 nsTArray
<OwningNonNull
<nsIContent
>>& aArrayOfContents
) {
8233 MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable());
8235 RefPtr
<Element
> editingHost
= ComputeEditingHost();
8236 if (MOZ_UNLIKELY(NS_WARN_IF(!editingHost
))) {
8237 return NS_ERROR_FAILURE
;
8240 // The idea here is to put the nodes into a minimal number of blockquotes.
8241 // When the user blockquotes something, they expect one blockquote. That
8242 // may not be possible (for instance, if they have two table cells selected,
8243 // you need two blockquotes inside the cells).
8244 RefPtr
<Element
> curBlock
;
8245 nsCOMPtr
<nsINode
> prevParent
;
8247 EditorDOMPoint pointToPutCaret
;
8248 for (auto& content
: aArrayOfContents
) {
8249 // If the node is a table element or list item, dive inside
8250 if (HTMLEditUtils::IsAnyTableElementButNotTable(content
) ||
8251 HTMLEditUtils::IsListItem(content
)) {
8252 // Forget any previous block
8255 AutoTArray
<OwningNonNull
<nsIContent
>, 24> childContents
;
8256 HTMLEditor::GetChildNodesOf(*content
, childContents
);
8257 nsresult rv
= MoveNodesIntoNewBlockquoteElement(childContents
);
8258 if (NS_FAILED(rv
)) {
8259 NS_WARNING("HTMLEditor::MoveNodesIntoNewBlockquoteElement() failed");
8264 // If the node has different parent than previous node, further nodes in a
8267 if (prevParent
!= content
->GetParentNode()) {
8268 // Forget any previous blockquote node we were using
8270 prevParent
= content
->GetParentNode();
8273 prevParent
= content
->GetParentNode();
8276 // If no curBlock, make one
8278 CreateElementResult createNewBlockQuoteElementResult
=
8279 InsertElementWithSplittingAncestorsWithTransaction(
8280 *nsGkAtoms::blockquote
, EditorDOMPoint(content
),
8281 BRElementNextToSplitPoint::Keep
, *editingHost
);
8282 if (createNewBlockQuoteElementResult
.isErr()) {
8284 "HTMLEditor::InsertElementWithSplittingAncestorsWithTransaction("
8285 "nsGkAtoms::blockquote) failed");
8286 return createNewBlockQuoteElementResult
.unwrapErr();
8288 nsresult rv
= createNewBlockQuoteElementResult
.SuggestCaretPointTo(
8289 *this, {SuggestCaret::OnlyIfHasSuggestion
,
8290 SuggestCaret::OnlyIfTransactionsAllowedToDoIt
});
8291 if (NS_FAILED(rv
)) {
8292 NS_WARNING("CreateElementResult::SuggestCaretPointTo() failed");
8295 RefPtr
<Element
> newBlockQuoteElement
=
8296 createNewBlockQuoteElementResult
.UnwrapNewNode();
8297 MOZ_ASSERT(newBlockQuoteElement
);
8298 // remember our new block for postprocessing
8299 // note: doesn't matter if we set mNewBlockElement multiple times.
8300 TopLevelEditSubActionDataRef().mNewBlockElement
= newBlockQuoteElement
;
8301 curBlock
= std::move(newBlockQuoteElement
);
8304 // MOZ_KnownLive because 'aArrayOfContents' is guaranteed to/ keep it alive.
8305 const MoveNodeResult moveNodeResult
=
8306 MoveNodeToEndWithTransaction(MOZ_KnownLive(content
), *curBlock
);
8307 if (moveNodeResult
.isErr()) {
8308 NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed");
8309 return moveNodeResult
.unwrapErr();
8311 nsresult rv
= moveNodeResult
.SuggestCaretPointTo(
8312 *this, {SuggestCaret::OnlyIfHasSuggestion
,
8313 SuggestCaret::OnlyIfTransactionsAllowedToDoIt
,
8314 SuggestCaret::AndIgnoreTrivialError
});
8315 if (NS_FAILED(rv
)) {
8316 NS_WARNING("MoveNodeResult::SuggestCaretPointTo() failed");
8319 NS_WARNING_ASSERTION(
8320 rv
!= NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR
,
8321 "MoveNodeResult::SuggestCaretPointTo() failed, but ignored");
8326 nsresult
HTMLEditor::RemoveBlockContainerElements(
8327 nsTArray
<OwningNonNull
<nsIContent
>>& aArrayOfContents
) {
8328 MOZ_ASSERT(IsEditActionDataAvailable());
8330 // Intent of this routine is to be used for converting to/from headers,
8331 // paragraphs, pre, and address. Those blocks that pretty much just contain
8333 RefPtr
<Element
> blockElement
;
8334 nsCOMPtr
<nsIContent
> firstContent
, lastContent
;
8335 for (auto& content
: aArrayOfContents
) {
8336 // If curNode is an <address>, <p>, <hn>, or <pre>, remove it.
8337 if (HTMLEditUtils::IsFormatNode(content
)) {
8338 // Process any partial progress saved
8340 SplitRangeOffFromNodeResult removeMiddleContainerResult
=
8341 SplitRangeOffFromBlockAndRemoveMiddleContainer(
8342 *blockElement
, *firstContent
, *lastContent
);
8343 if (removeMiddleContainerResult
.isErr()) {
8345 "HTMLEditor::SplitRangeOffFromBlockAndRemoveMiddleContainer() "
8347 return removeMiddleContainerResult
.unwrapErr();
8349 firstContent
= lastContent
= blockElement
= nullptr;
8351 if (!EditorUtils::IsEditableContent(content
, EditorType::HTML
)) {
8354 // Remove current block
8355 const Result
<EditorDOMPoint
, nsresult
> unwrapFormatBlockResult
=
8356 RemoveBlockContainerWithTransaction(
8357 MOZ_KnownLive(*content
->AsElement()));
8358 if (MOZ_UNLIKELY(unwrapFormatBlockResult
.isErr())) {
8359 NS_WARNING("HTMLEditor::RemoveBlockContainerWithTransaction() failed");
8360 return unwrapFormatBlockResult
.inspectErr();
8362 const EditorDOMPoint
& pointToPutCaret
= unwrapFormatBlockResult
.inspect();
8363 if (!AllowsTransactionsToChangeSelection() || !pointToPutCaret
.IsSet()) {
8366 nsresult rv
= CollapseSelectionTo(pointToPutCaret
);
8367 if (NS_FAILED(rv
)) {
8368 NS_WARNING("EditorBase::CollapseSelectionTo() failed");
8374 // XXX How about, <th>, <thead>, <tfoot>, <dt>, <dl>?
8375 if (content
->IsAnyOfHTMLElements(
8376 nsGkAtoms::table
, nsGkAtoms::tr
, nsGkAtoms::tbody
, nsGkAtoms::td
,
8377 nsGkAtoms::li
, nsGkAtoms::blockquote
, nsGkAtoms::div
) ||
8378 HTMLEditUtils::IsAnyListElement(content
)) {
8379 // Process any partial progress saved
8381 SplitRangeOffFromNodeResult removeMiddleContainerResult
=
8382 SplitRangeOffFromBlockAndRemoveMiddleContainer(
8383 *blockElement
, *firstContent
, *lastContent
);
8384 if (removeMiddleContainerResult
.isErr()) {
8386 "HTMLEditor::SplitRangeOffFromBlockAndRemoveMiddleContainer() "
8388 return removeMiddleContainerResult
.unwrapErr();
8390 firstContent
= lastContent
= blockElement
= nullptr;
8392 if (!EditorUtils::IsEditableContent(content
, EditorType::HTML
)) {
8396 AutoTArray
<OwningNonNull
<nsIContent
>, 24> childContents
;
8397 HTMLEditor::GetChildNodesOf(*content
, childContents
);
8398 nsresult rv
= RemoveBlockContainerElements(childContents
);
8399 if (NS_FAILED(rv
)) {
8400 NS_WARNING("HTMLEditor::RemoveBlockContainerElements() failed");
8406 if (HTMLEditUtils::IsInlineElement(content
)) {
8408 // If so, is this node a descendant?
8409 if (EditorUtils::IsDescendantOf(*content
, *blockElement
)) {
8410 // Then we don't need to do anything different for this node
8411 lastContent
= content
;
8414 // Otherwise, we have progressed beyond end of blockElement, so let's
8415 // handle it now. We need to remove the portion of blockElement that
8416 // contains [firstContent - lastContent].
8417 SplitRangeOffFromNodeResult removeMiddleContainerResult
=
8418 SplitRangeOffFromBlockAndRemoveMiddleContainer(
8419 *blockElement
, *firstContent
, *lastContent
);
8420 if (removeMiddleContainerResult
.isErr()) {
8422 "HTMLEditor::SplitRangeOffFromBlockAndRemoveMiddleContainer() "
8424 return removeMiddleContainerResult
.unwrapErr();
8426 firstContent
= lastContent
= blockElement
= nullptr;
8427 // Fall out and handle content
8429 blockElement
= HTMLEditUtils::GetAncestorElement(
8430 content
, HTMLEditUtils::ClosestEditableBlockElement
);
8431 if (!blockElement
|| !HTMLEditUtils::IsFormatNode(blockElement
) ||
8432 !HTMLEditUtils::IsRemovableNode(*blockElement
)) {
8433 // Not a block kind that we care about.
8434 blockElement
= nullptr;
8436 firstContent
= lastContent
= content
;
8442 // Some node that is already sans block style. Skip over it and process
8443 // any partial progress saved.
8444 SplitRangeOffFromNodeResult removeMiddleContainerResult
=
8445 SplitRangeOffFromBlockAndRemoveMiddleContainer(
8446 *blockElement
, *firstContent
, *lastContent
);
8447 if (removeMiddleContainerResult
.isErr()) {
8449 "HTMLEditor::SplitRangeOffFromBlockAndRemoveMiddleContainer() "
8451 return removeMiddleContainerResult
.unwrapErr();
8453 firstContent
= lastContent
= blockElement
= nullptr;
8457 // Process any partial progress saved
8459 SplitRangeOffFromNodeResult removeMiddleContainerResult
=
8460 SplitRangeOffFromBlockAndRemoveMiddleContainer(
8461 *blockElement
, *firstContent
, *lastContent
);
8462 if (removeMiddleContainerResult
.isErr()) {
8464 "HTMLEditor::SplitRangeOffFromBlockAndRemoveMiddleContainer() "
8466 return removeMiddleContainerResult
.unwrapErr();
8468 firstContent
= lastContent
= blockElement
= nullptr;
8473 nsresult
HTMLEditor::CreateOrChangeBlockContainerElement(
8474 nsTArray
<OwningNonNull
<nsIContent
>>& aArrayOfContents
, nsAtom
& aBlockTag
) {
8475 MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable());
8477 RefPtr
<Element
> editingHost
= ComputeEditingHost();
8478 if (MOZ_UNLIKELY(NS_WARN_IF(!editingHost
))) {
8479 return NS_ERROR_FAILURE
;
8482 // Intent of this routine is to be used for converting to/from headers,
8483 // paragraphs, pre, and address. Those blocks that pretty much just contain
8485 nsCOMPtr
<Element
> newBlock
;
8486 nsCOMPtr
<Element
> curBlock
;
8487 for (auto& content
: aArrayOfContents
) {
8488 EditorDOMPoint
atContent(content
);
8489 if (NS_WARN_IF(!atContent
.IsInContentNode())) {
8490 // If given node has been removed from the document, let's ignore it
8491 // since the following code may need its parent replace it with new
8498 // Is it already the right kind of block, or an uneditable block?
8499 if (content
->IsHTMLElement(&aBlockTag
) ||
8500 (!EditorUtils::IsEditableContent(content
, EditorType::HTML
) &&
8501 HTMLEditUtils::IsBlockElement(content
))) {
8502 // Forget any previous block used for previous inline nodes
8504 // Do nothing to this block
8508 // If content is a address, p, header, address, or pre, replace it with a
8509 // new block of correct type.
8510 // XXX: pre can't hold everything the others can
8511 if (HTMLEditUtils::IsMozDiv(content
) ||
8512 HTMLEditUtils::IsFormatNode(content
)) {
8513 // Forget any previous block used for previous inline nodes
8515 CreateElementResult newBlockElementOrError
=
8516 ReplaceContainerAndCloneAttributesWithTransaction(
8517 MOZ_KnownLive(*content
->AsElement()), aBlockTag
);
8518 if (newBlockElementOrError
.isErr()) {
8520 "EditorBase::ReplaceContainerAndCloneAttributesWithTransaction() "
8522 return newBlockElementOrError
.unwrapErr();
8524 // If the new block element was moved to different element or removed by
8525 // the web app via mutation event listener, we should stop handling this
8526 // action since we cannot handle each of a lot of edge cases.
8527 if (NS_WARN_IF(newBlockElementOrError
.GetNewNode()->GetParentNode() !=
8528 atContent
.GetContainer())) {
8529 return NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
;
8531 nsresult rv
= newBlockElementOrError
.SuggestCaretPointTo(
8532 *this, {SuggestCaret::OnlyIfHasSuggestion
,
8533 SuggestCaret::OnlyIfTransactionsAllowedToDoIt
,
8534 SuggestCaret::AndIgnoreTrivialError
});
8535 if (NS_FAILED(rv
)) {
8536 NS_WARNING("CreateElementResult::SuggestCaretPointTo() failed");
8539 NS_WARNING_ASSERTION(
8540 rv
!= NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR
,
8541 "CreateElementResult::SuggestCaretPointTo() failed, but ignored");
8542 newBlock
= newBlockElementOrError
.UnwrapNewNode();
8546 if (HTMLEditUtils::IsTable(content
) ||
8547 HTMLEditUtils::IsAnyListElement(content
) ||
8548 content
->IsAnyOfHTMLElements(nsGkAtoms::tbody
, nsGkAtoms::tr
,
8549 nsGkAtoms::td
, nsGkAtoms::li
,
8550 nsGkAtoms::blockquote
, nsGkAtoms::div
)) {
8551 // Forget any previous block used for previous inline nodes
8554 AutoTArray
<OwningNonNull
<nsIContent
>, 24> childContents
;
8555 HTMLEditor::GetChildNodesOf(*content
, childContents
);
8556 if (!childContents
.IsEmpty()) {
8558 CreateOrChangeBlockContainerElement(childContents
, aBlockTag
);
8559 if (NS_FAILED(rv
)) {
8561 "HTMLEditor::CreateOrChangeBlockContainerElement() failed");
8567 // Make sure we can put a block here
8568 CreateElementResult createNewBlockElementResult
=
8569 InsertElementWithSplittingAncestorsWithTransaction(
8570 aBlockTag
, atContent
, BRElementNextToSplitPoint::Keep
,
8572 if (createNewBlockElementResult
.isErr()) {
8576 "InsertElementWithSplittingAncestorsWithTransaction(%s) failed",
8577 nsAtomCString(&aBlockTag
).get())
8579 return createNewBlockElementResult
.unwrapErr();
8581 nsresult rv
= createNewBlockElementResult
.SuggestCaretPointTo(
8582 *this, {SuggestCaret::OnlyIfHasSuggestion
,
8583 SuggestCaret::OnlyIfTransactionsAllowedToDoIt
});
8584 if (NS_FAILED(rv
)) {
8585 NS_WARNING("CreateElementResult::SuggestCaretPointTo() failed");
8588 MOZ_ASSERT(createNewBlockElementResult
.GetNewNode());
8589 // Remember our new block for postprocessing
8590 TopLevelEditSubActionDataRef().mNewBlockElement
=
8591 createNewBlockElementResult
.UnwrapNewNode();
8595 if (content
->IsHTMLElement(nsGkAtoms::br
)) {
8596 // If the node is a break, we honor it by putting further nodes in a new
8599 // Forget any previous block used for previous inline nodes
8601 // MOZ_KnownLive because 'aArrayOfContents' is guaranteed to keep it
8603 nsresult rv
= DeleteNodeWithTransaction(MOZ_KnownLive(*content
));
8604 if (NS_FAILED(rv
)) {
8605 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
8611 // The break is the first (or even only) node we encountered. Create a
8613 CreateElementResult createNewBlockElementResult
=
8614 InsertElementWithSplittingAncestorsWithTransaction(
8615 aBlockTag
, atContent
, BRElementNextToSplitPoint::Keep
,
8617 if (createNewBlockElementResult
.isErr()) {
8621 "InsertElementWithSplittingAncestorsWithTransaction(%s) failed",
8622 nsAtomCString(&aBlockTag
).get())
8624 return createNewBlockElementResult
.unwrapErr();
8626 nsresult rv
= createNewBlockElementResult
.SuggestCaretPointTo(
8627 *this, {SuggestCaret::OnlyIfHasSuggestion
,
8628 SuggestCaret::OnlyIfTransactionsAllowedToDoIt
});
8629 if (NS_FAILED(rv
)) {
8630 NS_WARNING("CreateElementResult::SuggestCaretPointTo() failed");
8633 RefPtr
<Element
> newBlockElement
=
8634 createNewBlockElementResult
.UnwrapNewNode();
8635 MOZ_ASSERT(newBlockElement
);
8636 // Remember our new block for postprocessing
8637 // Note: doesn't matter if we set mNewBlockElement multiple times.
8638 TopLevelEditSubActionDataRef().mNewBlockElement
= newBlockElement
;
8639 // MOZ_KnownLive because 'aArrayOfContents' is guaranteed to keep it
8641 const MoveNodeResult moveNodeResult
= MoveNodeToEndWithTransaction(
8642 MOZ_KnownLive(content
), *newBlockElement
);
8643 if (moveNodeResult
.isErr()) {
8644 NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed");
8645 return moveNodeResult
.unwrapErr();
8647 rv
= moveNodeResult
.SuggestCaretPointTo(
8648 *this, {SuggestCaret::OnlyIfHasSuggestion
,
8649 SuggestCaret::OnlyIfTransactionsAllowedToDoIt
,
8650 SuggestCaret::AndIgnoreTrivialError
});
8651 if (NS_FAILED(rv
)) {
8652 NS_WARNING("MoveNodeResult::SuggestCaretPointTo() failed");
8655 NS_WARNING_ASSERTION(
8656 rv
!= NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR
,
8657 "MoveNodeResult::SuggestCaretPointTo() failed, but ignored");
8658 curBlock
= std::move(newBlockElement
);
8662 if (HTMLEditUtils::IsInlineElement(content
)) {
8663 // If content is inline, pull it into curBlock. Note: it's assumed that
8664 // consecutive inline nodes in aNodeArray are actually members of the
8665 // same block parent. This happens to be true now as a side effect of
8666 // how aNodeArray is contructed, but some additional logic should be
8667 // added here if that should change
8669 // If content is a non editable, drop it if we are going to <pre>.
8670 if (&aBlockTag
== nsGkAtoms::pre
&&
8671 !EditorUtils::IsEditableContent(content
, EditorType::HTML
)) {
8672 // Do nothing to this block
8676 // If no curBlock, make one
8678 CreateElementResult createNewBlockElementResult
=
8679 InsertElementWithSplittingAncestorsWithTransaction(
8680 aBlockTag
, atContent
, BRElementNextToSplitPoint::Keep
,
8682 if (createNewBlockElementResult
.isErr()) {
8683 NS_WARNING(nsPrintfCString("HTMLEditor::"
8684 "InsertElementWithSplittingAncestorsWithTr"
8685 "ansaction(%s) failed",
8686 nsAtomCString(&aBlockTag
).get())
8688 return createNewBlockElementResult
.unwrapErr();
8690 nsresult rv
= createNewBlockElementResult
.SuggestCaretPointTo(
8691 *this, {SuggestCaret::OnlyIfHasSuggestion
,
8692 SuggestCaret::OnlyIfTransactionsAllowedToDoIt
});
8693 if (NS_FAILED(rv
)) {
8694 NS_WARNING("CreateElementResult::SuggestCaretPointTo() failed");
8697 MOZ_ASSERT(createNewBlockElementResult
.GetNewNode());
8698 curBlock
= createNewBlockElementResult
.UnwrapNewNode();
8700 // Update container of content.
8701 atContent
.Set(content
);
8703 // Remember our new block for postprocessing
8704 // Note: doesn't matter if we set mNewBlockElement multiple times.
8705 TopLevelEditSubActionDataRef().mNewBlockElement
= curBlock
;
8708 if (NS_WARN_IF(!atContent
.IsSet())) {
8709 // This is possible due to mutation events, let's not assert
8710 return NS_ERROR_UNEXPECTED
;
8713 // XXX If content is a br, replace it with a return if going to <pre>
8715 // This is a continuation of some inline nodes that belong together in
8716 // the same block item. Use curBlock.
8718 // MOZ_KnownLive because 'aArrayOfContents' is guaranteed to keep it
8719 // alive. We could try to make that a rvalue ref and create a const array
8720 // on the stack here, but callers are passing in auto arrays, and we don't
8721 // want to introduce copies..
8722 const MoveNodeResult moveNodeResult
=
8723 MoveNodeToEndWithTransaction(MOZ_KnownLive(content
), *curBlock
);
8724 if (moveNodeResult
.isErr()) {
8725 NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed");
8726 return moveNodeResult
.unwrapErr();
8728 nsresult rv
= moveNodeResult
.SuggestCaretPointTo(
8729 *this, {SuggestCaret::OnlyIfHasSuggestion
,
8730 SuggestCaret::OnlyIfTransactionsAllowedToDoIt
,
8731 SuggestCaret::AndIgnoreTrivialError
});
8732 if (NS_FAILED(rv
)) {
8733 NS_WARNING("MoveNodeResult::SuggestCaretPointTo() failed");
8736 NS_WARNING_ASSERTION(
8737 rv
!= NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR
,
8738 "MoveNodeResult::SuggestCaretPointTo() failed, but ignored");
8744 SplitNodeResult
HTMLEditor::MaybeSplitAncestorsForInsertWithTransaction(
8745 nsAtom
& aTag
, const EditorDOMPoint
& aStartOfDeepestRightNode
) {
8746 MOZ_ASSERT(IsEditActionDataAvailable());
8748 if (NS_WARN_IF(!aStartOfDeepestRightNode
.IsSet())) {
8749 return SplitNodeResult(NS_ERROR_INVALID_ARG
);
8751 MOZ_ASSERT(aStartOfDeepestRightNode
.IsSetAndValid());
8753 RefPtr
<Element
> host
= ComputeEditingHost();
8754 if (NS_WARN_IF(!host
)) {
8755 return SplitNodeResult(NS_ERROR_FAILURE
);
8758 // The point must be descendant of editing host.
8759 if (aStartOfDeepestRightNode
.GetContainer() != host
&&
8760 !EditorUtils::IsDescendantOf(*aStartOfDeepestRightNode
.GetContainer(),
8762 NS_WARNING("aStartOfDeepestRightNode was not in editing host");
8763 return SplitNodeResult(NS_ERROR_INVALID_ARG
);
8766 // Look for a node that can legally contain the tag.
8767 EditorDOMPoint
pointToInsert(aStartOfDeepestRightNode
);
8768 for (; pointToInsert
.IsSet();
8769 pointToInsert
.Set(pointToInsert
.GetContainer())) {
8770 // We cannot split active editing host and its ancestor. So, there is
8771 // no element to contain the specified element.
8772 if (pointToInsert
.GetChild() == host
) {
8774 "HTMLEditor::MaybeSplitAncestorsForInsertWithTransaction() reached "
8776 return SplitNodeResult(NS_ERROR_FAILURE
);
8779 if (HTMLEditUtils::CanNodeContain(*pointToInsert
.GetContainer(), aTag
)) {
8780 // Found an ancestor node which can contain the element.
8785 MOZ_DIAGNOSTIC_ASSERT(pointToInsert
.IsSet());
8787 // If the point itself can contain the tag, we don't need to split any
8788 // ancestor nodes. In this case, we should return the given split point
8790 if (pointToInsert
.GetContainer() == aStartOfDeepestRightNode
.GetContainer()) {
8791 return SplitNodeResult::NotHandled(aStartOfDeepestRightNode
,
8792 SplitNodeDirection::LeftNodeIsNewOne
);
8795 SplitNodeResult splitNodeResult
= SplitNodeDeepWithTransaction(
8796 MOZ_KnownLive(*pointToInsert
.GetChild()), aStartOfDeepestRightNode
,
8797 SplitAtEdges::eAllowToCreateEmptyContainer
);
8798 NS_WARNING_ASSERTION(splitNodeResult
.isOk(),
8799 "HTMLEditor::SplitNodeDeepWithTransaction(SplitAtEdges::"
8800 "eAllowToCreateEmptyContainer) failed");
8801 return splitNodeResult
;
8805 HTMLEditor::InsertElementWithSplittingAncestorsWithTransaction(
8806 nsAtom
& aTagName
, const EditorDOMPoint
& aPointToInsert
,
8807 BRElementNextToSplitPoint aBRElementNextToSplitPoint
,
8808 const Element
& aEditingHost
,
8809 const InitializeInsertingElement
& aInitializer
) {
8810 MOZ_ASSERT(aPointToInsert
.IsSetAndValid());
8812 const SplitNodeResult splitNodeResult
=
8813 MaybeSplitAncestorsForInsertWithTransaction(aTagName
, aPointToInsert
);
8814 if (splitNodeResult
.isErr()) {
8816 "HTMLEditor::MaybeSplitAncestorsForInsertWithTransaction() failed");
8817 return CreateElementResult(splitNodeResult
.unwrapErr());
8819 DebugOnly
<bool> wasCaretPositionSuggestedAtSplit
=
8820 splitNodeResult
.HasCaretPointSuggestion();
8821 // We'll update selection below, and nobody touches selection until then.
8822 // Therefore, we don't need to touch selection here.
8823 splitNodeResult
.IgnoreCaretPointSuggestion();
8825 // If current handling node has been moved from the container by a
8826 // mutation event listener when we need to do something more for it,
8827 // we should stop handling this action since we cannot handle each of
8828 // a lot of edge cases.
8829 if (NS_WARN_IF(aPointToInsert
.HasChildMovedFromContainer())) {
8830 return CreateElementResult(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
);
8833 EditorDOMPoint splitPoint
= splitNodeResult
.AtSplitPoint
<EditorDOMPoint
>();
8835 if (aBRElementNextToSplitPoint
== BRElementNextToSplitPoint::Delete
) {
8836 // Consume a trailing br, if any. This is to keep an alignment from
8837 // creating extra lines, if possible.
8838 if (nsCOMPtr
<nsIContent
> maybeBRContent
= HTMLEditUtils::GetNextContent(
8840 {WalkTreeOption::IgnoreNonEditableNode
,
8841 WalkTreeOption::StopAtBlockBoundary
},
8843 if (maybeBRContent
->IsHTMLElement(nsGkAtoms::br
) &&
8844 splitPoint
.GetChild()) {
8845 // Making use of html structure... if next node after where we are
8846 // putting our div is not a block, then the br we found is in same
8847 // block we are, so it's safe to consume it.
8848 if (nsIContent
* nextEditableSibling
= HTMLEditUtils::GetNextSibling(
8849 *splitPoint
.GetChild(),
8850 {WalkTreeOption::IgnoreNonEditableNode
})) {
8851 if (!HTMLEditUtils::IsBlockElement(*nextEditableSibling
)) {
8852 AutoEditorDOMPointChildInvalidator
lockOffset(splitPoint
);
8853 nsresult rv
= DeleteNodeWithTransaction(*maybeBRContent
);
8854 if (NS_FAILED(rv
)) {
8855 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
8856 return CreateElementResult(rv
);
8864 CreateElementResult createNewElementResult
= CreateAndInsertElement(
8865 WithTransaction::Yes
, aTagName
, splitPoint
, aInitializer
);
8866 if (createNewElementResult
.isErr()) {
8868 "HTMLEditor::CreateAndInsertElement(WithTransaction::Yes) failed");
8869 return CreateElementResult(createNewElementResult
.unwrapErr());
8871 MOZ_ASSERT_IF(wasCaretPositionSuggestedAtSplit
,
8872 createNewElementResult
.HasCaretPointSuggestion());
8873 MOZ_ASSERT(createNewElementResult
.GetNewNode());
8875 // If the new block element was moved to different element or removed by
8876 // the web app via mutation event listener, we should stop handling this
8877 // action since we cannot handle each of a lot of edge cases.
8878 if (NS_WARN_IF(createNewElementResult
.GetNewNode()->GetParentNode() !=
8879 splitPoint
.GetContainer())) {
8880 return CreateElementResult(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
);
8883 return createNewElementResult
;
8886 nsresult
HTMLEditor::JoinNearestEditableNodesWithTransaction(
8887 nsIContent
& aNodeLeft
, nsIContent
& aNodeRight
,
8888 EditorDOMPoint
* aNewFirstChildOfRightNode
) {
8889 MOZ_ASSERT(IsEditActionDataAvailable());
8890 MOZ_ASSERT(aNewFirstChildOfRightNode
);
8892 // Caller responsible for left and right node being the same type
8893 if (NS_WARN_IF(!aNodeLeft
.GetParentNode())) {
8894 return NS_ERROR_FAILURE
;
8896 // If they don't have the same parent, first move the right node to after
8898 if (aNodeLeft
.GetParentNode() != aNodeRight
.GetParentNode()) {
8899 const MoveNodeResult moveNodeResult
=
8900 MoveNodeWithTransaction(aNodeRight
, EditorDOMPoint(&aNodeLeft
));
8901 if (moveNodeResult
.isErr()) {
8902 NS_WARNING("HTMLEditor::MoveNodeWithTransaction() failed");
8903 return moveNodeResult
.unwrapErr();
8905 nsresult rv
= moveNodeResult
.SuggestCaretPointTo(
8906 *this, {SuggestCaret::OnlyIfHasSuggestion
,
8907 SuggestCaret::OnlyIfTransactionsAllowedToDoIt
,
8908 SuggestCaret::AndIgnoreTrivialError
});
8909 if (NS_FAILED(rv
)) {
8910 NS_WARNING("MoveNodeResult::SuggestCaretPointTo() failed");
8913 NS_WARNING_ASSERTION(
8914 rv
!= NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR
,
8915 "MoveNodeResult::SuggestCaretPointTo() failed, but ignored");
8918 // Separate join rules for differing blocks
8919 if (HTMLEditUtils::IsAnyListElement(&aNodeLeft
) || aNodeLeft
.IsText()) {
8920 // For lists, merge shallow (wouldn't want to combine list items)
8921 JoinNodesResult joinNodesResult
=
8922 JoinNodesWithTransaction(aNodeLeft
, aNodeRight
);
8923 if (MOZ_UNLIKELY(joinNodesResult
.Failed())) {
8924 NS_WARNING("HTMLEditor::JoinNodesWithTransaction failed");
8925 return joinNodesResult
.Rv();
8927 *aNewFirstChildOfRightNode
=
8928 joinNodesResult
.AtJoinedPoint
<EditorDOMPoint
>();
8929 return joinNodesResult
.Rv();
8932 // Remember the last left child, and first right child
8933 nsCOMPtr
<nsIContent
> lastEditableChildOfLeftContent
=
8934 HTMLEditUtils::GetLastChild(aNodeLeft
,
8935 {WalkTreeOption::IgnoreNonEditableNode
});
8936 if (MOZ_UNLIKELY(NS_WARN_IF(!lastEditableChildOfLeftContent
))) {
8937 return NS_ERROR_FAILURE
;
8940 nsCOMPtr
<nsIContent
> firstEditableChildOfRightContent
=
8941 HTMLEditUtils::GetFirstChild(aNodeRight
,
8942 {WalkTreeOption::IgnoreNonEditableNode
});
8943 if (NS_WARN_IF(!firstEditableChildOfRightContent
)) {
8944 return NS_ERROR_FAILURE
;
8947 // For list items, divs, etc., merge smart
8948 JoinNodesResult joinNodesResult
=
8949 JoinNodesWithTransaction(aNodeLeft
, aNodeRight
);
8950 if (MOZ_UNLIKELY(joinNodesResult
.Failed())) {
8951 NS_WARNING("HTMLEditor::JoinNodesWithTransaction() failed");
8952 return joinNodesResult
.Rv();
8955 if ((lastEditableChildOfLeftContent
->IsText() ||
8956 lastEditableChildOfLeftContent
->IsElement()) &&
8957 HTMLEditUtils::CanContentsBeJoined(*lastEditableChildOfLeftContent
,
8958 *firstEditableChildOfRightContent
,
8959 StyleDifference::CompareIfElements
)) {
8960 nsresult rv
= JoinNearestEditableNodesWithTransaction(
8961 *lastEditableChildOfLeftContent
, *firstEditableChildOfRightContent
,
8962 aNewFirstChildOfRightNode
);
8963 NS_WARNING_ASSERTION(
8965 "HTMLEditor::JoinNearestEditableNodesWithTransaction() failed");
8968 *aNewFirstChildOfRightNode
= joinNodesResult
.AtJoinedPoint
<EditorDOMPoint
>();
8972 Element
* HTMLEditor::GetMostDistantAncestorMailCiteElement(
8973 const nsINode
& aNode
) const {
8974 Element
* mailCiteElement
= nullptr;
8975 const bool isPlaintextEditor
= IsInPlaintextMode();
8976 for (Element
* element
: aNode
.InclusiveAncestorsOfType
<Element
>()) {
8977 if ((isPlaintextEditor
&& element
->IsHTMLElement(nsGkAtoms::pre
)) ||
8978 HTMLEditUtils::IsMailCite(*element
)) {
8979 mailCiteElement
= element
;
8982 if (element
->IsHTMLElement(nsGkAtoms::body
)) {
8986 return mailCiteElement
;
8989 nsresult
HTMLEditor::CacheInlineStyles(nsIContent
& aContent
) {
8990 MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable());
8992 nsresult rv
= GetInlineStyles(
8993 aContent
, *TopLevelEditSubActionDataRef().mCachedInlineStyles
);
8994 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
8995 "HTMLEditor::GetInlineStyles() failed");
8999 nsresult
HTMLEditor::GetInlineStyles(nsIContent
& aContent
,
9000 AutoStyleCacheArray
& aStyleCacheArray
) {
9001 MOZ_ASSERT(IsEditActionDataAvailable());
9002 MOZ_ASSERT(aStyleCacheArray
.IsEmpty());
9004 bool useCSS
= IsCSSEnabled();
9006 for (nsStaticAtom
* property
: {nsGkAtoms::b
,
9024 nsGkAtoms::backgroundColor
,
9027 nsStaticAtom
*tag
, *attribute
;
9028 if (property
== nsGkAtoms::face
|| property
== nsGkAtoms::size
||
9029 property
== nsGkAtoms::color
) {
9030 tag
= nsGkAtoms::font
;
9031 attribute
= property
;
9034 attribute
= nullptr;
9036 // If type-in state is set, don't intervene
9037 bool typeInSet
, unused
;
9038 mTypeInState
->GetTypingState(typeInSet
, unused
, tag
, attribute
, nullptr);
9043 nsString value
; // Don't use nsAutoString here because it requires memcpy
9044 // at creating new StyleCache instance.
9045 // Don't use CSS for <font size>, we don't support it usefully (bug 780035)
9046 if (!useCSS
|| (property
== nsGkAtoms::size
)) {
9047 isSet
= HTMLEditUtils::IsInlineStyleSetByElement(
9048 aContent
, *tag
, attribute
, nullptr, &value
);
9050 Result
<bool, nsresult
> isComputedCSSEquivalentToHTMLInlineStyleOrError
=
9051 mCSSEditUtils
->IsComputedCSSEquivalentToHTMLInlineStyleSet(
9052 aContent
, MOZ_KnownLive(tag
), MOZ_KnownLive(attribute
), value
);
9053 if (isComputedCSSEquivalentToHTMLInlineStyleOrError
.isErr()) {
9055 "CSSEditUtils::IsComputedCSSEquivalentToHTMLInlineStyleSet failed");
9056 return isComputedCSSEquivalentToHTMLInlineStyleOrError
.unwrapErr();
9058 isSet
= isComputedCSSEquivalentToHTMLInlineStyleOrError
.unwrap();
9061 aStyleCacheArray
.AppendElement(StyleCache(tag
, attribute
, value
));
9067 nsresult
HTMLEditor::ReapplyCachedStyles() {
9068 MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable());
9070 // The idea here is to examine our cached list of styles and see if any have
9071 // been removed. If so, add typeinstate for them, so that they will be
9072 // reinserted when new content is added.
9074 if (TopLevelEditSubActionDataRef().mCachedInlineStyles
->IsEmpty() ||
9075 !SelectionRef().RangeCount()) {
9079 // remember if we are in css mode
9080 bool useCSS
= IsCSSEnabled();
9082 const RangeBoundary
& atStartOfSelection
=
9083 SelectionRef().GetRangeAt(0)->StartRef();
9084 nsCOMPtr
<nsIContent
> startContainerContent
=
9085 atStartOfSelection
.Container() &&
9086 atStartOfSelection
.Container()->IsContent()
9087 ? atStartOfSelection
.Container()->AsContent()
9089 if (NS_WARN_IF(!startContainerContent
)) {
9093 AutoStyleCacheArray styleCacheArrayAtInsertionPoint
;
9095 GetInlineStyles(*startContainerContent
, styleCacheArrayAtInsertionPoint
);
9096 if (NS_WARN_IF(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
9097 return NS_ERROR_EDITOR_DESTROYED
;
9099 if (NS_FAILED(rv
)) {
9100 NS_WARNING("HTMLEditor::GetInlineStyles() failed, but ignored");
9104 for (StyleCache
& styleCacheBeforeEdit
:
9105 *TopLevelEditSubActionDataRef().mCachedInlineStyles
) {
9106 bool isFirst
= false, isAny
= false, isAll
= false;
9107 nsAutoString currentValue
;
9109 // check computed style first in css case
9110 Result
<bool, nsresult
> isComputedCSSEquivalentToHTMLInlineStyleOrError
=
9111 mCSSEditUtils
->IsComputedCSSEquivalentToHTMLInlineStyleSet(
9112 *startContainerContent
, MOZ_KnownLive(styleCacheBeforeEdit
.Tag()),
9113 MOZ_KnownLive(styleCacheBeforeEdit
.GetAttribute()), currentValue
);
9114 if (isComputedCSSEquivalentToHTMLInlineStyleOrError
.isErr()) {
9116 "CSSEditUtils::IsComputedCSSEquivalentToHTMLInlineStyleSet() "
9118 return isComputedCSSEquivalentToHTMLInlineStyleOrError
.unwrapErr();
9120 isAny
= isComputedCSSEquivalentToHTMLInlineStyleOrError
.unwrap();
9123 // then check typeinstate and html style
9124 nsresult rv
= GetInlinePropertyBase(
9125 MOZ_KnownLive(*styleCacheBeforeEdit
.Tag()),
9126 MOZ_KnownLive(styleCacheBeforeEdit
.GetAttribute()),
9127 &styleCacheBeforeEdit
.Value(), &isFirst
, &isAny
, &isAll
,
9129 if (NS_FAILED(rv
)) {
9130 NS_WARNING("HTMLEditor::GetInlinePropertyBase() failed");
9134 // This style has disappeared through deletion. Let's add the styles to
9135 // mTypeInState when same style isn't applied to the node already.
9136 if (isAny
&& !IsStyleCachePreservingSubAction(GetTopLevelEditSubAction())) {
9139 AutoStyleCacheArray::index_type index
=
9140 styleCacheArrayAtInsertionPoint
.IndexOf(
9141 styleCacheBeforeEdit
.Tag(), styleCacheBeforeEdit
.GetAttribute());
9142 if (index
== AutoStyleCacheArray::NoIndex
||
9143 styleCacheBeforeEdit
.Value() !=
9144 styleCacheArrayAtInsertionPoint
.ElementAt(index
).Value()) {
9145 mTypeInState
->SetProp(styleCacheBeforeEdit
.Tag(),
9146 styleCacheBeforeEdit
.GetAttribute(),
9147 styleCacheBeforeEdit
.Value());
9153 nsresult
HTMLEditor::InsertBRElementToEmptyListItemsAndTableCellsInRange(
9154 const RawRangeBoundary
& aStartRef
, const RawRangeBoundary
& aEndRef
) {
9155 MOZ_ASSERT(IsEditActionDataAvailable());
9157 AutoTArray
<OwningNonNull
<Element
>, 64> arrayOfEmptyElements
;
9159 if (NS_FAILED(iter
.Init(aStartRef
, aEndRef
))) {
9160 NS_WARNING("DOMIterator::Init() failed");
9161 return NS_ERROR_FAILURE
;
9163 iter
.AppendNodesToArray(
9164 +[](nsINode
& aNode
, void* aSelf
) {
9165 MOZ_ASSERT(Element::FromNode(&aNode
));
9167 Element
* element
= aNode
.AsElement();
9168 if (!EditorUtils::IsEditableContent(*element
, EditorType::HTML
) ||
9169 (!HTMLEditUtils::IsListItem(element
) &&
9170 !HTMLEditUtils::IsTableCellOrCaption(*element
))) {
9173 return HTMLEditUtils::IsEmptyNode(
9174 *element
, {EmptyCheckOption::TreatSingleBRElementAsVisible
});
9176 arrayOfEmptyElements
, this);
9178 // Put padding <br> elements for empty <li> and <td>.
9179 EditorDOMPoint pointToPutCaret
;
9180 for (auto& emptyElement
: arrayOfEmptyElements
) {
9181 // Need to put br at END of node. It may have empty containers in it and
9182 // still pass the "IsEmptyNode" test, and we want the br's to be after
9183 // them. Also, we want the br to be after the selection if the selection
9185 EditorDOMPoint
endOfNode(EditorDOMPoint::AtEndOf(emptyElement
));
9186 CreateElementResult insertPaddingBRElementResult
=
9187 InsertPaddingBRElementForEmptyLastLineWithTransaction(endOfNode
);
9188 if (insertPaddingBRElementResult
.isErr()) {
9190 "HTMLEditor::InsertPaddingBRElementForEmptyLastLineWithTransaction() "
9192 return insertPaddingBRElementResult
.unwrapErr();
9194 insertPaddingBRElementResult
.MoveCaretPointTo(
9195 pointToPutCaret
, *this,
9196 {SuggestCaret::OnlyIfHasSuggestion
,
9197 SuggestCaret::OnlyIfTransactionsAllowedToDoIt
});
9199 if (pointToPutCaret
.IsSet()) {
9200 nsresult rv
= CollapseSelectionTo(pointToPutCaret
);
9201 if (MOZ_UNLIKELY(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
9203 "EditorBase::CollapseSelectionTo() caused destroying the editor");
9204 return NS_ERROR_EDITOR_DESTROYED
;
9206 NS_WARNING_ASSERTION(
9208 "EditorBase::CollapseSelectionTo() failed, but ignored");
9213 nsresult
HTMLEditor::EnsureCaretInBlockElement(Element
& aElement
) {
9214 MOZ_ASSERT(IsEditActionDataAvailable());
9215 MOZ_ASSERT(SelectionRef().IsCollapsed());
9217 const auto atCaret
= GetFirstSelectionStartPoint
<EditorRawDOMPoint
>();
9218 if (NS_WARN_IF(!atCaret
.IsSet())) {
9219 return NS_ERROR_FAILURE
;
9222 // Use ranges and RangeUtils::CompareNodeToRange() to compare selection
9223 // start to new block.
9224 RefPtr
<StaticRange
> staticRange
=
9225 StaticRange::Create(atCaret
.ToRawRangeBoundary(),
9226 atCaret
.ToRawRangeBoundary(), IgnoreErrors());
9228 NS_WARNING("StaticRange::Create() failed");
9229 return NS_ERROR_FAILURE
;
9232 bool nodeBefore
, nodeAfter
;
9233 nsresult rv
= RangeUtils::CompareNodeToRange(&aElement
, staticRange
,
9234 &nodeBefore
, &nodeAfter
);
9235 if (NS_FAILED(rv
)) {
9236 NS_WARNING("RangeUtils::CompareNodeToRange() failed");
9240 if (nodeBefore
&& nodeAfter
) {
9241 return NS_OK
; // selection is inside block
9245 // selection is after block. put at end of block.
9246 nsIContent
* lastEditableContent
= HTMLEditUtils::GetLastChild(
9247 aElement
, {WalkTreeOption::IgnoreNonEditableNode
});
9248 if (!lastEditableContent
) {
9249 lastEditableContent
= &aElement
;
9251 EditorRawDOMPoint endPoint
;
9252 if (lastEditableContent
->IsText() ||
9253 HTMLEditUtils::IsContainerNode(*lastEditableContent
)) {
9254 endPoint
.SetToEndOf(lastEditableContent
);
9256 endPoint
.SetAfter(lastEditableContent
);
9257 if (NS_WARN_IF(!endPoint
.IsSet())) {
9258 return NS_ERROR_FAILURE
;
9261 nsresult rv
= CollapseSelectionTo(endPoint
);
9262 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
9263 "EditorBase::CollapseSelectionTo() failed");
9267 // selection is before block. put at start of block.
9268 nsIContent
* firstEditableContent
= HTMLEditUtils::GetFirstChild(
9269 aElement
, {WalkTreeOption::IgnoreNonEditableNode
});
9270 if (!firstEditableContent
) {
9271 firstEditableContent
= &aElement
;
9273 EditorRawDOMPoint atStartOfBlock
;
9274 if (firstEditableContent
->IsText() ||
9275 HTMLEditUtils::IsContainerNode(*firstEditableContent
)) {
9276 atStartOfBlock
.Set(firstEditableContent
);
9278 atStartOfBlock
.Set(firstEditableContent
, 0);
9280 rv
= CollapseSelectionTo(atStartOfBlock
);
9281 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
9282 "EditorBase::CollapseSelectionTo() failed");
9286 void HTMLEditor::SetSelectionInterlinePosition() {
9287 MOZ_ASSERT(IsEditActionDataAvailable());
9288 MOZ_ASSERT(SelectionRef().IsCollapsed());
9290 // Get the (collapsed) selection location
9291 const nsRange
* firstRange
= SelectionRef().GetRangeAt(0);
9292 if (NS_WARN_IF(!firstRange
)) {
9296 EditorDOMPoint
atCaret(firstRange
->StartRef());
9297 if (NS_WARN_IF(!atCaret
.IsSet())) {
9300 MOZ_ASSERT(atCaret
.IsSetAndValid());
9302 // First, let's check to see if we are after a `<br>`. We take care of this
9303 // special-case first so that we don't accidentally fall through into one of
9304 // the other conditionals.
9305 // XXX Although I don't understand "interline position", if caret is
9306 // immediately after non-editable contents, but previous editable
9307 // content is `<br>`, does this do right thing?
9308 if (Element
* editingHost
= ComputeEditingHost()) {
9309 if (nsIContent
* previousEditableContentInBlock
=
9310 HTMLEditUtils::GetPreviousContent(
9312 {WalkTreeOption::IgnoreNonEditableNode
,
9313 WalkTreeOption::StopAtBlockBoundary
},
9315 if (previousEditableContentInBlock
->IsHTMLElement(nsGkAtoms::br
)) {
9316 DebugOnly
<nsresult
> rvIgnored
= SelectionRef().SetInterlinePosition(
9317 InterlinePosition::StartOfNextLine
);
9318 NS_WARNING_ASSERTION(
9319 NS_SUCCEEDED(rvIgnored
),
9320 "Selection::SetInterlinePosition(InterlinePosition::"
9321 "StartOfNextLine) failed, but ignored");
9327 if (!atCaret
.GetChild()) {
9331 // If caret is immediately after a block, set interline position to "right".
9332 // XXX Although I don't understand "interline position", if caret is
9333 // immediately after non-editable contents, but previous editable
9334 // content is a block, does this do right thing?
9335 if (nsIContent
* previousEditableContentInBlockAtCaret
=
9336 HTMLEditUtils::GetPreviousSibling(
9337 *atCaret
.GetChild(), {WalkTreeOption::IgnoreNonEditableNode
})) {
9338 if (HTMLEditUtils::IsBlockElement(*previousEditableContentInBlockAtCaret
)) {
9339 DebugOnly
<nsresult
> rvIgnored
= SelectionRef().SetInterlinePosition(
9340 InterlinePosition::StartOfNextLine
);
9341 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored
),
9342 "Selection::SetInterlinePosition(InterlinePosition::"
9343 "StartOfNextLine) failed, but ignored");
9348 // If caret is immediately before a block, set interline position to "left".
9349 // XXX Although I don't understand "interline position", if caret is
9350 // immediately before non-editable contents, but next editable
9351 // content is a block, does this do right thing?
9352 if (nsIContent
* nextEditableContentInBlockAtCaret
=
9353 HTMLEditUtils::GetNextSibling(
9354 *atCaret
.GetChild(), {WalkTreeOption::IgnoreNonEditableNode
})) {
9355 if (HTMLEditUtils::IsBlockElement(*nextEditableContentInBlockAtCaret
)) {
9356 DebugOnly
<nsresult
> rvIgnored
=
9357 SelectionRef().SetInterlinePosition(InterlinePosition::EndOfLine
);
9358 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored
),
9359 "Selection::SetInterlinePosition(InterlinePosition::"
9360 "EndOfLine) failed, but ignored");
9365 nsresult
HTMLEditor::AdjustCaretPositionAndEnsurePaddingBRElement(
9366 nsIEditor::EDirection aDirectionAndAmount
) {
9367 MOZ_ASSERT(IsEditActionDataAvailable());
9368 MOZ_ASSERT(SelectionRef().IsCollapsed());
9370 auto point
= GetFirstSelectionStartPoint
<EditorDOMPoint
>();
9371 if (NS_WARN_IF(!point
.IsInContentNode())) {
9372 return NS_ERROR_FAILURE
;
9375 // If selection start is not editable, climb up the tree until editable one.
9376 while (!EditorUtils::IsEditableContent(*point
.ContainerAsContent(),
9377 EditorType::HTML
)) {
9378 point
.Set(point
.GetContainer());
9379 if (NS_WARN_IF(!point
.IsInContentNode())) {
9380 return NS_ERROR_FAILURE
;
9384 // If caret is in empty block element, we need to insert a `<br>` element
9385 // because the block should have one-line height.
9386 // XXX Even if only a part of the block is editable, shouldn't we put
9387 // caret if the block element is now empty?
9388 if (Element
* const editableBlockElement
=
9389 HTMLEditUtils::GetInclusiveAncestorElement(
9390 *point
.ContainerAsContent(),
9391 HTMLEditUtils::ClosestEditableBlockElement
)) {
9392 if (editableBlockElement
&&
9393 HTMLEditUtils::IsEmptyNode(
9394 *editableBlockElement
,
9395 {EmptyCheckOption::TreatSingleBRElementAsVisible
}) &&
9396 HTMLEditUtils::CanNodeContain(*point
.GetContainer(), *nsGkAtoms::br
)) {
9397 Element
* bodyOrDocumentElement
= GetRoot();
9398 if (NS_WARN_IF(!bodyOrDocumentElement
)) {
9399 return NS_ERROR_FAILURE
;
9401 if (point
.GetContainer() == bodyOrDocumentElement
) {
9402 // Our root node is completely empty. Don't add a <br> here.
9403 // AfterEditInner() will add one for us when it calls
9404 // EditorBase::MaybeCreatePaddingBRElementForEmptyEditor().
9405 // XXX This kind of dependency between methods makes us spaghetti.
9406 // Let's handle it here later.
9407 // XXX This looks odd check. If active editing host is not a
9408 // `<body>`, what are we doing?
9411 CreateElementResult insertPaddingBRElementResult
=
9412 InsertPaddingBRElementForEmptyLastLineWithTransaction(point
);
9413 if (insertPaddingBRElementResult
.isErr()) {
9415 "HTMLEditor::InsertPaddingBRElementForEmptyLastLineWithTransaction("
9417 return insertPaddingBRElementResult
.unwrapErr();
9419 nsresult rv
= insertPaddingBRElementResult
.SuggestCaretPointTo(
9420 *this, {SuggestCaret::OnlyIfHasSuggestion
,
9421 SuggestCaret::OnlyIfTransactionsAllowedToDoIt
,
9422 SuggestCaret::AndIgnoreTrivialError
});
9423 if (NS_FAILED(rv
)) {
9424 NS_WARNING("CreateElementResult::SuggestCaretPointTo() failed");
9427 NS_WARNING_ASSERTION(
9428 rv
!= NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR
,
9429 "CreateElementResult::SuggestCaretPointTo() failed, but ignored");
9434 // XXX Perhaps, we should do something if we're in a data node but not
9436 if (point
.IsInTextNode()) {
9440 // Do we need to insert a padding <br> element for empty last line? We do
9442 // 1) prior node is in same block where selection is AND
9443 // 2) prior node is a br AND
9444 // 3) that br is not visible
9445 RefPtr
<Element
> editingHost
= ComputeEditingHost();
9450 if (nsCOMPtr
<nsIContent
> previousEditableContent
=
9451 HTMLEditUtils::GetPreviousContent(
9452 point
, {WalkTreeOption::IgnoreNonEditableNode
}, editingHost
)) {
9453 // If caret and previous editable content are in same block element
9454 // (even if it's a non-editable element), we should put a padding <br>
9455 // element at end of the block.
9456 const Element
* const blockElementContainingCaret
=
9457 HTMLEditUtils::GetInclusiveAncestorElement(
9458 *point
.ContainerAsContent(), HTMLEditUtils::ClosestBlockElement
);
9459 const Element
* const blockElementContainingPreviousEditableContent
=
9460 HTMLEditUtils::GetAncestorElement(*previousEditableContent
,
9461 HTMLEditUtils::ClosestBlockElement
);
9462 // If previous editable content of caret is in same block and a `<br>`
9463 // element, we need to adjust interline position.
9464 if (blockElementContainingCaret
&&
9465 blockElementContainingCaret
==
9466 blockElementContainingPreviousEditableContent
&&
9467 point
.ContainerAsContent()->GetEditingHost() ==
9468 previousEditableContent
->GetEditingHost() &&
9469 previousEditableContent
&&
9470 previousEditableContent
->IsHTMLElement(nsGkAtoms::br
)) {
9471 // If it's an invisible `<br>` element, we need to insert a padding
9472 // `<br>` element for making empty line have one-line height.
9473 if (HTMLEditUtils::IsInvisibleBRElement(*previousEditableContent
)) {
9474 CreateElementResult insertPaddingBRElementResult
=
9475 InsertPaddingBRElementForEmptyLastLineWithTransaction(point
);
9476 if (insertPaddingBRElementResult
.isErr()) {
9479 "InsertPaddingBRElementForEmptyLastLineWithTransaction() failed");
9480 return insertPaddingBRElementResult
.unwrapErr();
9482 insertPaddingBRElementResult
.IgnoreCaretPointSuggestion();
9483 nsresult rv
= CollapseSelectionTo(
9484 EditorRawDOMPoint(insertPaddingBRElementResult
.GetNewNode(),
9485 InterlinePosition::StartOfNextLine
));
9486 if (NS_FAILED(rv
)) {
9487 NS_WARNING("EditorBase::CollapseSelectionTo() failed");
9491 // If it's a visible `<br>` element and next editable content is a
9492 // padding `<br>` element, we need to set interline position.
9493 else if (nsIContent
* nextEditableContentInBlock
=
9494 HTMLEditUtils::GetNextContent(
9495 *previousEditableContent
,
9496 {WalkTreeOption::IgnoreNonEditableNode
,
9497 WalkTreeOption::StopAtBlockBoundary
},
9499 if (EditorUtils::IsPaddingBRElementForEmptyLastLine(
9500 *nextEditableContentInBlock
)) {
9501 // Make it stick to the padding `<br>` element so that it will be
9503 DebugOnly
<nsresult
> rvIgnored
= SelectionRef().SetInterlinePosition(
9504 InterlinePosition::StartOfNextLine
);
9505 NS_WARNING_ASSERTION(
9506 NS_SUCCEEDED(rvIgnored
),
9507 "Selection::SetInterlinePosition(InterlinePosition::"
9508 "StartOfNextLine) failed, but ignored");
9514 // If previous editable content in same block is `<br>`, text node, `<img>`
9515 // or `<hr>`, current caret position is fine.
9516 if (nsIContent
* previousEditableContentInBlock
=
9517 HTMLEditUtils::GetPreviousContent(
9519 {WalkTreeOption::IgnoreNonEditableNode
,
9520 WalkTreeOption::StopAtBlockBoundary
},
9522 if (previousEditableContentInBlock
->IsHTMLElement(nsGkAtoms::br
) ||
9523 previousEditableContentInBlock
->IsText() ||
9524 HTMLEditUtils::IsImage(previousEditableContentInBlock
) ||
9525 previousEditableContentInBlock
->IsHTMLElement(nsGkAtoms::hr
)) {
9530 // If next editable content in same block is `<br>`, text node, `<img>` or
9531 // `<hr>`, current caret position is fine.
9532 if (nsIContent
* nextEditableContentInBlock
=
9533 HTMLEditUtils::GetNextContent(point
,
9534 {WalkTreeOption::IgnoreNonEditableNode
,
9535 WalkTreeOption::StopAtBlockBoundary
},
9537 if (nextEditableContentInBlock
->IsText() ||
9538 nextEditableContentInBlock
->IsAnyOfHTMLElements(
9539 nsGkAtoms::br
, nsGkAtoms::img
, nsGkAtoms::hr
)) {
9544 // Otherwise, look for a near editable content towards edit action direction.
9546 // If there is no editable content, keep current caret position.
9547 // XXX Why do we treat `nsIEditor::ePreviousWord` etc as forward direction?
9548 nsIContent
* nearEditableContent
= HTMLEditUtils::GetAdjacentContentToPutCaret(
9550 aDirectionAndAmount
== nsIEditor::ePrevious
? WalkTreeDirection::Backward
9551 : WalkTreeDirection::Forward
,
9553 if (!nearEditableContent
) {
9557 EditorRawDOMPoint pointToPutCaret
=
9558 HTMLEditUtils::GetGoodCaretPointFor
<EditorRawDOMPoint
>(
9559 *nearEditableContent
, aDirectionAndAmount
);
9560 if (!pointToPutCaret
.IsSet()) {
9561 NS_WARNING("HTMLEditUtils::GetGoodCaretPointFor() failed");
9562 return NS_ERROR_FAILURE
;
9564 nsresult rv
= CollapseSelectionTo(pointToPutCaret
);
9565 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
9566 "EditorBase::CollapseSelectionTo() failed");
9570 nsresult
HTMLEditor::RemoveEmptyNodesIn(nsRange
& aRange
) {
9571 MOZ_ASSERT(IsEditActionDataAvailable());
9572 MOZ_ASSERT(aRange
.IsPositioned());
9574 // Some general notes on the algorithm used here: the goal is to examine all
9575 // the nodes in aRange, and remove the empty ones. We do this by
9576 // using a content iterator to traverse all the nodes in the range, and
9577 // placing the empty nodes into an array. After finishing the iteration,
9578 // we delete the empty nodes in the array. (They cannot be deleted as we
9579 // find them because that would invalidate the iterator.)
9581 // Since checking to see if a node is empty can be costly for nodes with
9582 // many descendants, there are some optimizations made. I rely on the fact
9583 // that the iterator is post-order: it will visit children of a node before
9584 // visiting the parent node. So if I find that a child node is not empty, I
9585 // know that its parent is not empty without even checking. So I put the
9586 // parent on a "skipList" which is just a voidArray of nodes I can skip the
9587 // empty check on. If I encounter a node on the skiplist, i skip the
9588 // processing for that node and replace its slot in the skiplist with that
9591 // An interesting idea is to go ahead and regard parent nodes that are NOT
9592 // on the skiplist as being empty (without even doing the IsEmptyNode check)
9593 // on the theory that if they weren't empty, we would have encountered a
9594 // non-empty child earlier and thus put this parent node on the skiplist.
9596 // Unfortunately I can't use that strategy here, because the range may
9597 // include some children of a node while excluding others. Thus I could
9598 // find all the _examined_ children empty, but still not have an empty
9601 PostContentIterator postOrderIter
;
9602 nsresult rv
= postOrderIter
.Init(&aRange
);
9603 if (NS_FAILED(rv
)) {
9604 NS_WARNING("PostContentIterator::Init() failed");
9608 AutoTArray
<OwningNonNull
<nsIContent
>, 64> arrayOfEmptyContents
,
9609 arrayOfEmptyCites
, skipList
;
9611 // Check for empty nodes
9612 for (; !postOrderIter
.IsDone(); postOrderIter
.Next()) {
9613 MOZ_ASSERT(postOrderIter
.GetCurrentNode()->IsContent());
9615 nsIContent
* content
= postOrderIter
.GetCurrentNode()->AsContent();
9616 nsIContent
* parentContent
= content
->GetParent();
9618 size_t idx
= skipList
.IndexOf(content
);
9619 if (idx
!= skipList
.NoIndex
) {
9620 // This node is on our skip list. Skip processing for this node, and
9621 // replace its value in the skip list with the value of its parent
9622 if (parentContent
) {
9623 skipList
[idx
] = parentContent
;
9628 bool isCandidate
= false;
9629 bool isMailCite
= false;
9630 if (content
->IsElement()) {
9631 if (content
->IsHTMLElement(nsGkAtoms::body
)) {
9632 // Don't delete the body
9633 } else if ((isMailCite
=
9634 HTMLEditUtils::IsMailCite(*content
->AsElement())) ||
9635 content
->IsHTMLElement(nsGkAtoms::a
) ||
9636 HTMLEditUtils::IsInlineStyle(content
) ||
9637 HTMLEditUtils::IsAnyListElement(content
) ||
9638 content
->IsHTMLElement(nsGkAtoms::div
)) {
9639 // Only consider certain nodes to be empty for purposes of removal
9641 } else if (HTMLEditUtils::IsFormatNode(content
) ||
9642 HTMLEditUtils::IsListItem(content
) ||
9643 content
->IsHTMLElement(nsGkAtoms::blockquote
)) {
9644 // These node types are candidates if selection is not in them. If
9645 // it is one of these, don't delete if selection inside. This is so
9646 // we can create empty headings, etc., for the user to type into.
9647 AutoRangeArray
selectionRanges(SelectionRef());
9650 .IsAtLeastOneContainerOfRangeBoundariesInclusiveDescendantOf(
9655 bool isEmptyNode
= false;
9657 // We delete mailcites even if they have a solo br in them. Other
9658 // nodes we require to be empty.
9659 HTMLEditUtils::EmptyCheckOptions options
{
9660 EmptyCheckOption::TreatListItemAsVisible
,
9661 EmptyCheckOption::TreatTableCellAsVisible
};
9663 options
+= EmptyCheckOption::TreatSingleBRElementAsVisible
;
9665 isEmptyNode
= HTMLEditUtils::IsEmptyNode(*content
, options
);
9668 // mailcites go on a separate list from other empty nodes
9669 arrayOfEmptyCites
.AppendElement(*content
);
9671 arrayOfEmptyContents
.AppendElement(*content
);
9676 if (!isEmptyNode
&& parentContent
) {
9677 // put parent on skip list
9678 skipList
.AppendElement(*parentContent
);
9682 // now delete the empty nodes
9683 for (OwningNonNull
<nsIContent
>& emptyContent
: arrayOfEmptyContents
) {
9684 // XXX Shouldn't we check whether it's removable from its parent??
9685 if (HTMLEditUtils::IsSimplyEditableNode(emptyContent
)) {
9686 // MOZ_KnownLive because 'arrayOfEmptyContents' is guaranteed to keep it
9688 nsresult rv
= DeleteNodeWithTransaction(MOZ_KnownLive(emptyContent
));
9689 if (NS_FAILED(rv
)) {
9690 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
9696 // Now delete the empty mailcites. This is a separate step because we want
9697 // to pull out any br's and preserve them.
9698 EditorDOMPoint pointToPutCaret
;
9699 for (OwningNonNull
<nsIContent
>& emptyCite
: arrayOfEmptyCites
) {
9700 if (!HTMLEditUtils::IsEmptyNode(
9701 emptyCite
, {EmptyCheckOption::TreatSingleBRElementAsVisible
,
9702 EmptyCheckOption::TreatListItemAsVisible
,
9703 EmptyCheckOption::TreatTableCellAsVisible
})) {
9704 // We are deleting a cite that has just a `<br>`. We want to delete cite,
9705 // but preserve `<br>`.
9706 CreateElementResult insertBRElementResult
=
9707 InsertBRElement(WithTransaction::Yes
, EditorDOMPoint(emptyCite
));
9708 if (insertBRElementResult
.isErr()) {
9709 NS_WARNING("HTMLEditor::InsertBRElement(WithTransaction::Yes) failed");
9710 return insertBRElementResult
.unwrapErr();
9712 // XXX Is this intentional selection change?
9713 insertBRElementResult
.MoveCaretPointTo(
9714 pointToPutCaret
, *this,
9715 {SuggestCaret::OnlyIfHasSuggestion
,
9716 SuggestCaret::OnlyIfTransactionsAllowedToDoIt
});
9717 MOZ_ASSERT(insertBRElementResult
.GetNewNode());
9719 // MOZ_KnownLive because 'arrayOfEmptyCites' is guaranteed to keep it alive.
9720 nsresult rv
= DeleteNodeWithTransaction(MOZ_KnownLive(emptyCite
));
9721 if (NS_FAILED(rv
)) {
9722 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
9726 // XXX Is this intentional selection change?
9727 if (pointToPutCaret
.IsSet()) {
9728 nsresult rv
= CollapseSelectionTo(pointToPutCaret
);
9729 if (MOZ_UNLIKELY(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
9731 "EditorBase::CollapseSelectionTo() caused destroying the editor");
9732 return NS_ERROR_EDITOR_DESTROYED
;
9734 NS_WARNING_ASSERTION(
9736 "EditorBase::CollapseSelectionTo() failed, but ignored");
9742 nsresult
HTMLEditor::LiftUpListItemElement(
9743 Element
& aListItemElement
,
9744 LiftUpFromAllParentListElements aLiftUpFromAllParentListElements
) {
9745 MOZ_ASSERT(IsEditActionDataAvailable());
9747 if (!HTMLEditUtils::IsListItem(&aListItemElement
)) {
9748 return NS_ERROR_INVALID_ARG
;
9751 if (NS_WARN_IF(!aListItemElement
.GetParentElement()) ||
9752 NS_WARN_IF(!aListItemElement
.GetParentElement()->GetParentNode())) {
9753 return NS_ERROR_FAILURE
;
9756 // if it's first or last list item, don't need to split the list
9758 bool isFirstListItem
= HTMLEditUtils::IsFirstChild(
9759 aListItemElement
, {WalkTreeOption::IgnoreNonEditableNode
});
9760 bool isLastListItem
= HTMLEditUtils::IsLastChild(
9761 aListItemElement
, {WalkTreeOption::IgnoreNonEditableNode
});
9763 Element
* leftListElement
= aListItemElement
.GetParentElement();
9764 if (NS_WARN_IF(!leftListElement
)) {
9765 return NS_ERROR_FAILURE
;
9768 // If it's at middle of parent list element, split the parent list element.
9769 // Then, aListItem becomes the first list item of the right list element.
9770 if (!isFirstListItem
&& !isLastListItem
) {
9771 EditorDOMPoint
atListItemElement(&aListItemElement
);
9772 if (NS_WARN_IF(!atListItemElement
.IsSet())) {
9773 return NS_ERROR_FAILURE
;
9775 MOZ_ASSERT(atListItemElement
.IsSetAndValid());
9776 const SplitNodeResult splitListItemParentResult
=
9777 SplitNodeWithTransaction(atListItemElement
);
9778 if (splitListItemParentResult
.isErr()) {
9779 NS_WARNING("HTMLEditor::SplitNodeWithTransaction() failed");
9780 return splitListItemParentResult
.unwrapErr();
9782 nsresult rv
= splitListItemParentResult
.SuggestCaretPointTo(
9783 *this, {SuggestCaret::OnlyIfTransactionsAllowedToDoIt
});
9784 if (NS_FAILED(rv
)) {
9785 NS_WARNING("SplitNodeResult::SuggestCaretPointTo() failed");
9790 Element::FromNodeOrNull(splitListItemParentResult
.GetPreviousContent());
9791 if (MOZ_UNLIKELY(!leftListElement
)) {
9793 "HTMLEditor::SplitNodeWithTransaction() didn't return left list "
9795 return NS_ERROR_FAILURE
;
9799 // In most cases, insert the list item into the new left list node..
9800 EditorDOMPoint
pointToInsertListItem(leftListElement
);
9801 if (NS_WARN_IF(!pointToInsertListItem
.IsSet())) {
9802 return NS_ERROR_FAILURE
;
9805 // But when the list item was the first child of the right list, it should
9806 // be inserted between the both list elements. This allows user to hit
9807 // Enter twice at a list item breaks the parent list node.
9808 if (!isFirstListItem
) {
9809 DebugOnly
<bool> advanced
= pointToInsertListItem
.AdvanceOffset();
9810 NS_WARNING_ASSERTION(advanced
,
9811 "Failed to advance offset to right list node");
9814 EditorDOMPoint pointToPutCaret
;
9815 MoveNodeResult moveListItemElementResult
=
9816 MoveNodeWithTransaction(aListItemElement
, pointToInsertListItem
);
9817 if (moveListItemElementResult
.isErr()) {
9818 NS_WARNING("HTMLEditor::MoveNodeWithTransaction() failed");
9819 return moveListItemElementResult
.unwrapErr();
9821 moveListItemElementResult
.MoveCaretPointTo(
9822 pointToPutCaret
, *this,
9823 {SuggestCaret::OnlyIfHasSuggestion
,
9824 SuggestCaret::OnlyIfTransactionsAllowedToDoIt
});
9826 // Unwrap list item contents if they are no longer in a list
9827 // XXX If the parent list element is a child of another list element
9828 // (although invalid tree), the list item element won't be unwrapped.
9829 // That makes the parent ancestor element tree valid, but might be
9830 // unexpected result.
9831 // XXX If aListItemElement is <dl> or <dd> and current parent is <ul> or <ol>,
9832 // the list items won't be unwrapped. If aListItemElement is <li> and its
9833 // current parent is <dl>, there is same issue.
9834 if (!HTMLEditUtils::IsAnyListElement(pointToInsertListItem
.GetContainer()) &&
9835 HTMLEditUtils::IsListItem(&aListItemElement
)) {
9836 Result
<EditorDOMPoint
, nsresult
> unwrapOrphanListItemElementResult
=
9837 RemoveBlockContainerWithTransaction(aListItemElement
);
9838 if (MOZ_UNLIKELY(unwrapOrphanListItemElementResult
.isErr())) {
9839 NS_WARNING("HTMLEditor::RemoveBlockContainerWithTransaction() failed");
9840 return unwrapOrphanListItemElementResult
.unwrapErr();
9842 if (AllowsTransactionsToChangeSelection() &&
9843 unwrapOrphanListItemElementResult
.inspect().IsSet()) {
9844 pointToPutCaret
= unwrapOrphanListItemElementResult
.unwrap();
9846 if (!pointToPutCaret
.IsSet()) {
9849 nsresult rv
= CollapseSelectionTo(pointToPutCaret
);
9850 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
9851 "EditorBase::CollapseSelectionTo() failed");
9855 if (pointToPutCaret
.IsSet()) {
9856 nsresult rv
= CollapseSelectionTo(pointToPutCaret
);
9857 if (MOZ_UNLIKELY(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
9858 NS_WARNING("EditorBase::CollapseSelectionTo() failed");
9861 NS_WARNING_ASSERTION(
9863 "EditorBase::CollapseSelectionTo() failed, but ignored");
9866 if (aLiftUpFromAllParentListElements
== LiftUpFromAllParentListElements::No
) {
9869 // XXX If aListItemElement is moved to unexpected element by mutation event
9870 // listener, shouldn't we stop calling this?
9871 nsresult rv
= LiftUpListItemElement(aListItemElement
,
9872 LiftUpFromAllParentListElements::Yes
);
9873 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
9874 "HTMLEditor::LiftUpListItemElement("
9875 "LiftUpFromAllParentListElements::Yes) failed");
9879 nsresult
HTMLEditor::DestroyListStructureRecursively(Element
& aListElement
) {
9880 MOZ_ASSERT(IsEditActionDataAvailable());
9881 MOZ_ASSERT(HTMLEditUtils::IsAnyListElement(&aListElement
));
9883 // XXX If mutation event listener inserts new child into `aListElement`,
9884 // this becomes infinite loop so that we should set limit of the
9885 // loop count from original child count.
9886 while (aListElement
.GetFirstChild()) {
9887 OwningNonNull
<nsIContent
> child
= *aListElement
.GetFirstChild();
9889 if (HTMLEditUtils::IsListItem(child
)) {
9890 // XXX Using LiftUpListItemElement() is too expensive for this purpose.
9891 // Looks like the reason why this method uses it is, only this loop
9892 // wants to work with first child of aListElement. However, what it
9893 // actually does is removing <li> as container. Perhaps, we should
9894 // decide destination first, and then, move contents in `child`.
9895 // XXX If aListElement is is a child of another list element (although
9896 // it's invalid tree), this moves the list item to outside of
9897 // aListElement's parent. Is that really intentional behavior?
9898 nsresult rv
= LiftUpListItemElement(
9899 MOZ_KnownLive(*child
->AsElement()),
9900 HTMLEditor::LiftUpFromAllParentListElements::Yes
);
9901 if (NS_FAILED(rv
)) {
9903 "HTMLEditor::LiftUpListItemElement(LiftUpFromAllParentListElements:"
9910 if (HTMLEditUtils::IsAnyListElement(child
)) {
9912 DestroyListStructureRecursively(MOZ_KnownLive(*child
->AsElement()));
9913 if (NS_FAILED(rv
)) {
9914 NS_WARNING("HTMLEditor::DestroyListStructureRecursively() failed");
9920 // Delete any non-list items for now
9921 // XXX This is not HTML5 aware. HTML5 allows all list elements to have
9922 // <script> and <template> and <dl> element to have <div> to group
9923 // some <dt> and <dd> elements. So, this may break valid children.
9924 nsresult rv
= DeleteNodeWithTransaction(*child
);
9925 if (NS_FAILED(rv
)) {
9926 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
9931 // Delete the now-empty list
9932 const Result
<EditorDOMPoint
, nsresult
> unwrapListElementResult
=
9933 RemoveBlockContainerWithTransaction(aListElement
);
9934 if (MOZ_UNLIKELY(unwrapListElementResult
.isErr())) {
9935 NS_WARNING("HTMLEditor::RemoveBlockContainerWithTransaction() failed");
9936 return unwrapListElementResult
.inspectErr();
9938 const EditorDOMPoint
& pointToPutCaret
= unwrapListElementResult
.inspect();
9939 if (!AllowsTransactionsToChangeSelection() || !pointToPutCaret
.IsSet()) {
9942 nsresult rv
= CollapseSelectionTo(pointToPutCaret
);
9943 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
9944 "EditorBase::CollapseSelectionTo() failed");
9948 nsresult
HTMLEditor::EnsureSelectionInBodyOrDocumentElement() {
9949 MOZ_ASSERT(IsEditActionDataAvailable());
9951 RefPtr
<Element
> bodyOrDocumentElement
= GetRoot();
9952 if (NS_WARN_IF(!bodyOrDocumentElement
)) {
9953 return NS_ERROR_FAILURE
;
9956 const auto atCaret
= GetFirstSelectionStartPoint
<EditorRawDOMPoint
>();
9957 if (NS_WARN_IF(!atCaret
.IsSet())) {
9958 return NS_ERROR_FAILURE
;
9961 // XXX This does wrong things. Web apps can put any elements as sibling
9962 // of `<body>` element. Therefore, this collapses `Selection` into
9963 // the `<body>` element which `HTMLDocument.body` is set to. So,
9964 // this makes users impossible to modify content outside of the
9965 // `<body>` element even if caret is in an editing host.
9967 // Check that selection start container is inside the <body> element.
9968 // XXXsmaug this code is insane.
9969 nsINode
* temp
= atCaret
.GetContainer();
9970 while (temp
&& !temp
->IsHTMLElement(nsGkAtoms::body
)) {
9971 temp
= temp
->GetParentOrShadowHostNode();
9974 // If we aren't in the <body> element, force the issue.
9976 nsresult rv
= CollapseSelectionToStartOf(*bodyOrDocumentElement
);
9977 if (MOZ_UNLIKELY(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
9979 "EditorBase::CollapseSelectionToStartOf() caused destroying the "
9981 return NS_ERROR_EDITOR_DESTROYED
;
9983 NS_WARNING_ASSERTION(
9985 "EditorBase::CollapseSelectionToStartOf() failed, but ignored");
9989 const auto selectionEndPoint
= GetFirstSelectionEndPoint
<EditorRawDOMPoint
>();
9990 if (NS_WARN_IF(!selectionEndPoint
.IsSet())) {
9991 return NS_ERROR_FAILURE
;
9994 // check that selNode is inside body
9995 // XXXsmaug this code is insane.
9996 temp
= selectionEndPoint
.GetContainer();
9997 while (temp
&& !temp
->IsHTMLElement(nsGkAtoms::body
)) {
9998 temp
= temp
->GetParentOrShadowHostNode();
10001 // If we aren't in the <body> element, force the issue.
10003 nsresult rv
= CollapseSelectionToStartOf(*bodyOrDocumentElement
);
10004 if (MOZ_UNLIKELY(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
10006 "EditorBase::CollapseSelectionToStartOf() caused destroying the "
10008 return NS_ERROR_EDITOR_DESTROYED
;
10010 NS_WARNING_ASSERTION(
10012 "EditorBase::CollapseSelectionToStartOf() failed, but ignored");
10018 nsresult
HTMLEditor::InsertPaddingBRElementForEmptyLastLineIfNeeded(
10019 Element
& aElement
) {
10020 MOZ_ASSERT(IsEditActionDataAvailable());
10022 if (!HTMLEditUtils::IsBlockElement(aElement
)) {
10026 if (!HTMLEditUtils::IsEmptyNode(
10027 aElement
, {EmptyCheckOption::TreatSingleBRElementAsVisible
})) {
10031 CreateElementResult insertPaddingBRElementResult
=
10032 InsertPaddingBRElementForEmptyLastLineWithTransaction(
10033 EditorDOMPoint(&aElement
, 0u));
10034 if (insertPaddingBRElementResult
.isErr()) {
10036 "HTMLEditor::InsertPaddingBRElementForEmptyLastLineWithTransaction() "
10038 return insertPaddingBRElementResult
.unwrapErr();
10040 nsresult rv
= insertPaddingBRElementResult
.SuggestCaretPointTo(
10041 *this, {SuggestCaret::OnlyIfHasSuggestion
,
10042 SuggestCaret::OnlyIfTransactionsAllowedToDoIt
,
10043 SuggestCaret::AndIgnoreTrivialError
});
10044 if (NS_FAILED(rv
)) {
10045 NS_WARNING("CreateElementResult::SuggestCaretPointTo() failed");
10048 NS_WARNING_ASSERTION(
10049 rv
!= NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR
,
10050 "CreateElementResult::SuggestCaretPointTo() failed, but ignored");
10054 nsresult
HTMLEditor::InsertBRElementIfEmptyBlockElement(Element
& aElement
) {
10055 MOZ_ASSERT(IsEditActionDataAvailable());
10057 if (!HTMLEditUtils::IsBlockElement(aElement
)) {
10061 if (!HTMLEditUtils::IsEmptyNode(
10062 aElement
, {EmptyCheckOption::TreatSingleBRElementAsVisible
})) {
10066 CreateElementResult insertBRElementResult
=
10067 InsertBRElement(WithTransaction::Yes
, EditorDOMPoint(&aElement
, 0u));
10068 if (insertBRElementResult
.isErr()) {
10069 NS_WARNING("HTMLEditor::InsertBRElement(WithTransaction::Yes) failed");
10070 return insertBRElementResult
.unwrapErr();
10072 // XXX Is this intentional selection change?
10073 nsresult rv
= insertBRElementResult
.SuggestCaretPointTo(
10074 *this, {SuggestCaret::OnlyIfHasSuggestion
,
10075 SuggestCaret::OnlyIfTransactionsAllowedToDoIt
,
10076 SuggestCaret::AndIgnoreTrivialError
});
10077 if (NS_FAILED(rv
)) {
10078 NS_WARNING("CreateElementResult::SuggestCaretPointTo() failed");
10081 NS_WARNING_ASSERTION(
10082 rv
!= NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR
,
10083 "CreateElementResult::SuggestCaretPointTo() failed, but ignored");
10084 MOZ_ASSERT(insertBRElementResult
.GetNewNode());
10088 nsresult
HTMLEditor::RemoveAlignFromDescendants(Element
& aElement
,
10089 const nsAString
& aAlignType
,
10090 EditTarget aEditTarget
) {
10091 MOZ_ASSERT(IsEditActionDataAvailable());
10092 MOZ_ASSERT(!aElement
.IsHTMLElement(nsGkAtoms::table
));
10094 bool useCSS
= IsCSSEnabled();
10096 // Let's remove all alignment hints in the children of aNode; it can
10097 // be an ALIGN attribute (in case we just remove it) or a CENTER
10098 // element (here we have to remove the container and keep its
10099 // children). We break on tables and don't look at their children.
10100 nsCOMPtr
<nsIContent
> nextSibling
;
10101 for (nsIContent
* content
=
10102 aEditTarget
== EditTarget::NodeAndDescendantsExceptTable
10104 : aElement
.GetFirstChild();
10105 content
; content
= nextSibling
) {
10106 // Get the next sibling before removing content from the DOM tree.
10107 // XXX If next sibling is removed from the parent and/or inserted to
10108 // different parent, we will behave unexpectedly. I think that
10109 // we should create child list and handle it with checking whether
10110 // it's still a child of expected parent.
10111 nextSibling
= aEditTarget
== EditTarget::NodeAndDescendantsExceptTable
10113 : content
->GetNextSibling();
10115 if (content
->IsHTMLElement(nsGkAtoms::center
)) {
10116 OwningNonNull
<Element
> centerElement
= *content
->AsElement();
10117 nsresult rv
= RemoveAlignFromDescendants(
10118 centerElement
, aAlignType
, EditTarget::OnlyDescendantsExceptTable
);
10119 if (NS_FAILED(rv
)) {
10121 "HTMLEditor::RemoveAlignFromDescendants(EditTarget::"
10122 "OnlyDescendantsExceptTable) failed");
10126 // We may have to insert a `<br>` element before first child of the
10127 // `<center>` element because it should be first element of a hard line
10128 // even after removing the `<center>` element.
10129 rv
= EnsureHardLineBeginsWithFirstChildOf(centerElement
);
10130 if (NS_FAILED(rv
)) {
10131 NS_WARNING("HTMLEditor::EnsureHardLineBeginsWithFirstChildOf() failed");
10135 // We may have to insert a `<br>` element after last child of the
10136 // `<center>` element because it should be last element of a hard line
10137 // even after removing the `<center>` element.
10138 rv
= EnsureHardLineEndsWithLastChildOf(centerElement
);
10139 if (NS_FAILED(rv
)) {
10140 NS_WARNING("HTMLEditor::EnsureHardLineEndsWithLastChildOf() failed");
10144 const Result
<EditorDOMPoint
, nsresult
> unwrapCenterElementResult
=
10145 RemoveContainerWithTransaction(centerElement
);
10146 if (MOZ_UNLIKELY(unwrapCenterElementResult
.isErr())) {
10147 NS_WARNING("HTMLEditor::RemoveContainerWithTransaction() failed");
10148 return unwrapCenterElementResult
.inspectErr();
10150 const EditorDOMPoint
& pointToPutCaret
=
10151 unwrapCenterElementResult
.inspect();
10152 if (!AllowsTransactionsToChangeSelection() && !pointToPutCaret
.IsSet()) {
10155 if (NS_FAILED(rv
= CollapseSelectionTo(pointToPutCaret
))) {
10156 NS_WARNING("EditorBase::CollapseSelectionTo() failed");
10162 if (!HTMLEditUtils::IsBlockElement(*content
) &&
10163 !content
->IsHTMLElement(nsGkAtoms::hr
)) {
10167 OwningNonNull
<Element
> blockOrHRElement
= *content
->AsElement();
10168 if (HTMLEditUtils::SupportsAlignAttr(blockOrHRElement
)) {
10170 RemoveAttributeWithTransaction(blockOrHRElement
, *nsGkAtoms::align
);
10171 if (NS_WARN_IF(Destroyed())) {
10172 return NS_ERROR_EDITOR_DESTROYED
;
10174 if (NS_FAILED(rv
)) {
10176 "EditorBase::RemoveAttributeWithTransaction(nsGkAtoms::align) "
10182 if (blockOrHRElement
->IsAnyOfHTMLElements(nsGkAtoms::table
,
10184 nsresult rv
= SetAttributeOrEquivalent(
10185 blockOrHRElement
, nsGkAtoms::align
, aAlignType
, false);
10186 if (NS_WARN_IF(Destroyed())) {
10187 return NS_ERROR_EDITOR_DESTROYED
;
10189 if (NS_FAILED(rv
)) {
10191 "EditorBase::SetAttributeOrEquivalent(nsGkAtoms::align) failed");
10195 nsStyledElement
* styledBlockOrHRElement
=
10196 nsStyledElement::FromNode(blockOrHRElement
);
10197 if (NS_WARN_IF(!styledBlockOrHRElement
)) {
10198 return NS_ERROR_FAILURE
;
10200 // MOZ_KnownLive(*styledBlockOrHRElement): It's `blockOrHRElement
10201 // which is OwningNonNull.
10202 nsAutoString dummyCssValue
;
10203 nsresult rv
= mCSSEditUtils
->RemoveCSSInlineStyleWithTransaction(
10204 MOZ_KnownLive(*styledBlockOrHRElement
), nsGkAtoms::textAlign
,
10206 if (NS_FAILED(rv
)) {
10208 "CSSEditUtils::RemoveCSSInlineStyleWithTransaction(nsGkAtoms::"
10209 "textAlign) failed");
10214 if (!blockOrHRElement
->IsHTMLElement(nsGkAtoms::table
)) {
10215 // unless this is a table, look at children
10216 nsresult rv
= RemoveAlignFromDescendants(
10217 blockOrHRElement
, aAlignType
, EditTarget::OnlyDescendantsExceptTable
);
10218 if (NS_FAILED(rv
)) {
10220 "HTMLEditor::RemoveAlignFromDescendants(EditTarget::"
10221 "OnlyDescendantsExceptTable) failed");
10229 nsresult
HTMLEditor::EnsureHardLineBeginsWithFirstChildOf(
10230 Element
& aRemovingContainerElement
) {
10231 MOZ_ASSERT(IsEditActionDataAvailable());
10233 nsIContent
* firstEditableChild
= HTMLEditUtils::GetFirstChild(
10234 aRemovingContainerElement
, {WalkTreeOption::IgnoreNonEditableNode
});
10235 if (!firstEditableChild
) {
10239 if (HTMLEditUtils::IsBlockElement(*firstEditableChild
) ||
10240 firstEditableChild
->IsHTMLElement(nsGkAtoms::br
)) {
10244 nsIContent
* previousEditableContent
= HTMLEditUtils::GetPreviousSibling(
10245 aRemovingContainerElement
, {WalkTreeOption::IgnoreNonEditableNode
});
10246 if (!previousEditableContent
) {
10250 if (HTMLEditUtils::IsBlockElement(*previousEditableContent
) ||
10251 previousEditableContent
->IsHTMLElement(nsGkAtoms::br
)) {
10255 CreateElementResult insertBRElementResult
= InsertBRElement(
10256 WithTransaction::Yes
, EditorDOMPoint(&aRemovingContainerElement
, 0u));
10257 if (insertBRElementResult
.isErr()) {
10258 NS_WARNING("HTMLEditor::InsertBRElement(WithTransaction::Yes) failed");
10259 return insertBRElementResult
.unwrapErr();
10261 // XXX Is this intentional selection change?
10262 nsresult rv
= insertBRElementResult
.SuggestCaretPointTo(
10263 *this, {SuggestCaret::OnlyIfHasSuggestion
,
10264 SuggestCaret::OnlyIfTransactionsAllowedToDoIt
,
10265 SuggestCaret::AndIgnoreTrivialError
});
10266 if (NS_FAILED(rv
)) {
10267 NS_WARNING("CreateElementResult::SuggestCaretPointTo() failed");
10270 NS_WARNING_ASSERTION(
10271 rv
!= NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR
,
10272 "CreateElementResult::SuggestCaretPointTo() failed, but ignored");
10273 MOZ_ASSERT(insertBRElementResult
.GetNewNode());
10277 nsresult
HTMLEditor::EnsureHardLineEndsWithLastChildOf(
10278 Element
& aRemovingContainerElement
) {
10279 MOZ_ASSERT(IsEditActionDataAvailable());
10281 nsIContent
* firstEditableContent
= HTMLEditUtils::GetLastChild(
10282 aRemovingContainerElement
, {WalkTreeOption::IgnoreNonEditableNode
});
10283 if (!firstEditableContent
) {
10287 if (HTMLEditUtils::IsBlockElement(*firstEditableContent
) ||
10288 firstEditableContent
->IsHTMLElement(nsGkAtoms::br
)) {
10292 nsIContent
* nextEditableContent
= HTMLEditUtils::GetPreviousSibling(
10293 aRemovingContainerElement
, {WalkTreeOption::IgnoreNonEditableNode
});
10294 if (!nextEditableContent
) {
10298 if (HTMLEditUtils::IsBlockElement(*nextEditableContent
) ||
10299 nextEditableContent
->IsHTMLElement(nsGkAtoms::br
)) {
10303 CreateElementResult insertBRElementResult
= InsertBRElement(
10304 WithTransaction::Yes
, EditorDOMPoint::AtEndOf(aRemovingContainerElement
));
10305 if (insertBRElementResult
.isErr()) {
10306 NS_WARNING("HTMLEditor::InsertBRElement(WithTransaction::Yes) failed");
10307 return insertBRElementResult
.unwrapErr();
10309 nsresult rv
= insertBRElementResult
.SuggestCaretPointTo(
10310 *this, {SuggestCaret::OnlyIfHasSuggestion
,
10311 SuggestCaret::OnlyIfTransactionsAllowedToDoIt
,
10312 SuggestCaret::AndIgnoreTrivialError
});
10313 if (NS_FAILED(rv
)) {
10314 NS_WARNING("CreateElementResult::SuggestCaretPointTo() failed");
10317 NS_WARNING_ASSERTION(
10318 rv
!= NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR
,
10319 "CreateElementResult::SuggestCaretPointTo() failed, but ignored");
10320 MOZ_ASSERT(insertBRElementResult
.GetNewNode());
10324 nsresult
HTMLEditor::SetBlockElementAlign(Element
& aBlockOrHRElement
,
10325 const nsAString
& aAlignType
,
10326 EditTarget aEditTarget
) {
10327 MOZ_ASSERT(IsEditActionDataAvailable());
10328 MOZ_ASSERT(HTMLEditUtils::IsBlockElement(aBlockOrHRElement
) ||
10329 aBlockOrHRElement
.IsHTMLElement(nsGkAtoms::hr
));
10330 MOZ_ASSERT(IsCSSEnabled() ||
10331 HTMLEditUtils::SupportsAlignAttr(aBlockOrHRElement
));
10333 if (!aBlockOrHRElement
.IsHTMLElement(nsGkAtoms::table
)) {
10335 RemoveAlignFromDescendants(aBlockOrHRElement
, aAlignType
, aEditTarget
);
10336 if (NS_FAILED(rv
)) {
10337 NS_WARNING("HTMLEditor::RemoveAlignFromDescendants() failed");
10341 nsresult rv
= SetAttributeOrEquivalent(&aBlockOrHRElement
, nsGkAtoms::align
,
10342 aAlignType
, false);
10343 if (NS_WARN_IF(Destroyed())) {
10344 return NS_ERROR_EDITOR_DESTROYED
;
10346 NS_WARNING_ASSERTION(
10348 "HTMLEditor::SetAttributeOrEquivalent(nsGkAtoms::align) failed");
10352 nsresult
HTMLEditor::ChangeMarginStart(Element
& aElement
,
10353 ChangeMargin aChangeMargin
) {
10354 MOZ_ASSERT(IsEditActionDataAvailable());
10356 nsStaticAtom
& marginProperty
= MarginPropertyAtomForIndent(aElement
);
10357 if (NS_WARN_IF(Destroyed())) {
10358 return NS_ERROR_EDITOR_DESTROYED
;
10360 nsAutoString value
;
10361 DebugOnly
<nsresult
> rvIgnored
=
10362 CSSEditUtils::GetSpecifiedProperty(aElement
, marginProperty
, value
);
10363 if (NS_WARN_IF(Destroyed())) {
10364 return NS_ERROR_EDITOR_DESTROYED
;
10366 NS_WARNING_ASSERTION(
10367 NS_SUCCEEDED(rvIgnored
),
10368 "CSSEditUtils::GetSpecifiedProperty() failed, but ignored");
10370 RefPtr
<nsAtom
> unit
;
10371 CSSEditUtils::ParseLength(value
, &f
, getter_AddRefs(unit
));
10373 nsAutoString defaultLengthUnit
;
10374 CSSEditUtils::GetDefaultLengthUnit(defaultLengthUnit
);
10375 unit
= NS_Atomize(defaultLengthUnit
);
10377 int8_t multiplier
= aChangeMargin
== ChangeMargin::Increase
? 1 : -1;
10378 if (nsGkAtoms::in
== unit
) {
10379 f
+= NS_EDITOR_INDENT_INCREMENT_IN
* multiplier
;
10380 } else if (nsGkAtoms::cm
== unit
) {
10381 f
+= NS_EDITOR_INDENT_INCREMENT_CM
* multiplier
;
10382 } else if (nsGkAtoms::mm
== unit
) {
10383 f
+= NS_EDITOR_INDENT_INCREMENT_MM
* multiplier
;
10384 } else if (nsGkAtoms::pt
== unit
) {
10385 f
+= NS_EDITOR_INDENT_INCREMENT_PT
* multiplier
;
10386 } else if (nsGkAtoms::pc
== unit
) {
10387 f
+= NS_EDITOR_INDENT_INCREMENT_PC
* multiplier
;
10388 } else if (nsGkAtoms::em
== unit
) {
10389 f
+= NS_EDITOR_INDENT_INCREMENT_EM
* multiplier
;
10390 } else if (nsGkAtoms::ex
== unit
) {
10391 f
+= NS_EDITOR_INDENT_INCREMENT_EX
* multiplier
;
10392 } else if (nsGkAtoms::px
== unit
) {
10393 f
+= NS_EDITOR_INDENT_INCREMENT_PX
* multiplier
;
10394 } else if (nsGkAtoms::percentage
== unit
) {
10395 f
+= NS_EDITOR_INDENT_INCREMENT_PERCENT
* multiplier
;
10399 if (nsStyledElement
* styledElement
= nsStyledElement::FromNode(&aElement
)) {
10400 nsAutoString newValue
;
10401 newValue
.AppendFloat(f
);
10402 newValue
.Append(nsDependentAtomString(unit
));
10403 // MOZ_KnownLive(*styledElement): It's aElement and its lifetime must
10404 // be guaranteed by caller because of MOZ_CAN_RUN_SCRIPT method.
10405 // MOZ_KnownLive(merginProperty): It's nsStaticAtom.
10406 nsresult rv
= mCSSEditUtils
->SetCSSPropertyWithTransaction(
10407 MOZ_KnownLive(*styledElement
), MOZ_KnownLive(marginProperty
),
10409 if (rv
== NS_ERROR_EDITOR_DESTROYED
) {
10411 "CSSEditUtils::SetCSSPropertyWithTransaction() destroyed the "
10413 return NS_ERROR_EDITOR_DESTROYED
;
10415 NS_WARNING_ASSERTION(
10417 "CSSEditUtils::SetCSSPropertyWithTransaction() failed, but ignored");
10422 if (nsStyledElement
* styledElement
= nsStyledElement::FromNode(&aElement
)) {
10423 // MOZ_KnownLive(*styledElement): It's aElement and its lifetime must
10424 // be guaranteed by caller because of MOZ_CAN_RUN_SCRIPT method.
10425 // MOZ_KnownLive(merginProperty): It's nsStaticAtom.
10426 nsresult rv
= mCSSEditUtils
->RemoveCSSPropertyWithTransaction(
10427 MOZ_KnownLive(*styledElement
), MOZ_KnownLive(marginProperty
), value
);
10428 if (rv
== NS_ERROR_EDITOR_DESTROYED
) {
10430 "CSSEditUtils::RemoveCSSPropertyWithTransaction() destroyed the "
10432 return NS_ERROR_EDITOR_DESTROYED
;
10434 NS_WARNING_ASSERTION(
10436 "CSSEditUtils::RemoveCSSPropertyWithTransaction() failed, but ignored");
10439 // Remove unnecessary divs
10440 if (!aElement
.IsHTMLElement(nsGkAtoms::div
) ||
10441 HTMLEditor::HasAttributes(&aElement
)) {
10444 // Don't touch editiong host nor node which is outside of it.
10445 Element
* editingHost
= ComputeEditingHost();
10446 if (&aElement
== editingHost
||
10447 !aElement
.IsInclusiveDescendantOf(editingHost
)) {
10451 const Result
<EditorDOMPoint
, nsresult
> unwrapDivElementResult
=
10452 RemoveContainerWithTransaction(aElement
);
10453 if (MOZ_UNLIKELY(unwrapDivElementResult
.isErr())) {
10454 NS_WARNING("HTMLEditor::RemoveContainerWithTransaction() failed");
10455 return unwrapDivElementResult
.inspectErr();
10457 const EditorDOMPoint
& pointToPutCaret
= unwrapDivElementResult
.inspect();
10458 if (!AllowsTransactionsToChangeSelection() || !pointToPutCaret
.IsSet()) {
10461 nsresult rv
= CollapseSelectionTo(pointToPutCaret
);
10462 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
10463 "EditorBase::CollapseSelectionTo() failed");
10467 EditActionResult
HTMLEditor::SetSelectionToAbsoluteAsSubAction() {
10468 MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable());
10470 AutoPlaceholderBatch
treatAsOneTransaction(
10471 *this, ScrollSelectionIntoView::Yes
, __FUNCTION__
);
10472 IgnoredErrorResult ignoredError
;
10473 AutoEditSubActionNotifier
startToHandleEditSubAction(
10474 *this, EditSubAction::eSetPositionToAbsolute
, nsIEditor::eNext
,
10476 if (NS_WARN_IF(ignoredError
.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED
))) {
10477 return EditActionResult(ignoredError
.StealNSResult());
10479 NS_WARNING_ASSERTION(
10480 !ignoredError
.Failed(),
10481 "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
10483 EditActionResult result
= CanHandleHTMLEditSubAction();
10484 if (result
.Failed() || result
.Canceled()) {
10485 NS_WARNING_ASSERTION(result
.Succeeded(),
10486 "HTMLEditor::CanHandleHTMLEditSubAction() failed");
10490 nsresult rv
= EnsureNoPaddingBRElementForEmptyEditor();
10491 if (NS_WARN_IF(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
10492 return EditActionResult(NS_ERROR_EDITOR_DESTROYED
);
10494 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
10495 "EditorBase::EnsureNoPaddingBRElementForEmptyEditor() "
10496 "failed, but ignored");
10498 if (NS_SUCCEEDED(rv
) && SelectionRef().IsCollapsed()) {
10499 nsresult rv
= EnsureCaretNotAfterInvisibleBRElement();
10500 if (NS_WARN_IF(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
10501 return EditActionResult(NS_ERROR_EDITOR_DESTROYED
);
10503 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
10504 "HTMLEditor::EnsureCaretNotAfterInvisibleBRElement() "
10505 "failed, but ignored");
10506 if (NS_SUCCEEDED(rv
)) {
10507 nsresult rv
= PrepareInlineStylesForCaret();
10508 if (NS_WARN_IF(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
10509 return EditActionResult(NS_ERROR_EDITOR_DESTROYED
);
10511 NS_WARNING_ASSERTION(
10513 "HTMLEditgor::PrepareInlineStylesForCaret() failed, but ignored");
10517 RefPtr
<Element
> focusElement
= GetSelectionContainerElement();
10518 if (focusElement
&& HTMLEditUtils::IsImage(focusElement
)) {
10519 TopLevelEditSubActionDataRef().mNewBlockElement
= std::move(focusElement
);
10520 return EditActionHandled();
10523 if (!SelectionRef().IsCollapsed()) {
10524 nsresult rv
= MaybeExtendSelectionToHardLineEdgesForBlockEditAction();
10525 if (NS_FAILED(rv
)) {
10527 "HTMLEditor::MaybeExtendSelectionToHardLineEdgesForBlockEditAction() "
10529 return EditActionHandled(rv
);
10533 // XXX Is this right thing?
10534 TopLevelEditSubActionDataRef().mNewBlockElement
= nullptr;
10536 RefPtr
<Element
> divElement
;
10537 rv
= MoveSelectedContentsToDivElementToMakeItAbsolutePosition(
10538 address_of(divElement
));
10539 // MoveSelectedContentsToDivElementToMakeItAbsolutePosition() may restore
10540 // selection with AutoSelectionRestorer. Therefore, the editor might have
10541 // already been destroyed now.
10542 if (NS_WARN_IF(Destroyed())) {
10543 return EditActionHandled(NS_ERROR_EDITOR_DESTROYED
);
10545 if (NS_FAILED(rv
)) {
10547 "HTMLEditor::MoveSelectedContentsToDivElementToMakeItAbsolutePosition()"
10549 return EditActionHandled(rv
);
10552 if (IsSelectionRangeContainerNotContent()) {
10553 NS_WARNING("Mutation event listener might have changed the selection");
10554 return EditActionHandled(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
);
10557 rv
= MaybeInsertPaddingBRElementForEmptyLastLineAtSelection();
10558 if (NS_WARN_IF(NS_FAILED(rv
))) {
10559 return EditActionHandled(rv
);
10563 return EditActionHandled();
10566 rv
= SetPositionToAbsoluteOrStatic(*divElement
, true);
10567 if (NS_WARN_IF(Destroyed())) {
10568 return EditActionHandled(NS_ERROR_EDITOR_DESTROYED
);
10570 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
10571 "HTMLEditor::SetPositionToAbsoluteOrStatic() failed");
10573 TopLevelEditSubActionDataRef().mNewBlockElement
= std::move(divElement
);
10574 return EditActionHandled(rv
);
10577 nsresult
HTMLEditor::MoveSelectedContentsToDivElementToMakeItAbsolutePosition(
10578 RefPtr
<Element
>* aTargetElement
) {
10579 MOZ_ASSERT(IsEditActionDataAvailable());
10580 MOZ_ASSERT(aTargetElement
);
10582 RefPtr
<Element
> editingHost
= ComputeEditingHost();
10583 if (MOZ_UNLIKELY(NS_WARN_IF(!editingHost
))) {
10584 return NS_ERROR_FAILURE
;
10587 AutoSelectionRestorer
restoreSelectionLater(*this);
10589 AutoTArray
<RefPtr
<nsRange
>, 4> arrayOfRanges
;
10590 GetSelectionRangesExtendedToHardLineStartAndEnd(
10591 arrayOfRanges
, EditSubAction::eSetPositionToAbsolute
);
10593 // Use these ranges to construct a list of nodes to act on.
10594 AutoTArray
<OwningNonNull
<nsIContent
>, 64> arrayOfContents
;
10595 nsresult rv
= SplitInlinesAndCollectEditTargetNodes(
10596 arrayOfRanges
, arrayOfContents
, EditSubAction::eSetPositionToAbsolute
,
10597 CollectNonEditableNodes::Yes
);
10598 if (NS_FAILED(rv
)) {
10600 "HTMLEditor::SplitInlinesAndCollectEditTargetNodes("
10601 "eSetPositionToAbsolute, CollectNonEditableNodes::Yes) failed");
10605 // If there is no visible and editable nodes in the edit targets, make an
10607 // XXX Isn't this odd if there are only non-editable visible nodes?
10608 if (HTMLEditUtils::IsEmptyOneHardLine(arrayOfContents
)) {
10609 const nsRange
* firstRange
= SelectionRef().GetRangeAt(0);
10610 if (NS_WARN_IF(!firstRange
)) {
10611 return NS_ERROR_FAILURE
;
10614 EditorDOMPoint
atCaret(firstRange
->StartRef());
10615 if (NS_WARN_IF(!atCaret
.IsSet())) {
10616 return NS_ERROR_FAILURE
;
10619 // Make sure we can put a block here.
10620 CreateElementResult createNewDivElementResult
=
10621 InsertElementWithSplittingAncestorsWithTransaction(
10622 *nsGkAtoms::div
, atCaret
, BRElementNextToSplitPoint::Keep
,
10624 if (createNewDivElementResult
.isErr()) {
10626 "HTMLEditor::InsertElementWithSplittingAncestorsWithTransaction("
10627 "nsGkAtoms::div) failed");
10628 return createNewDivElementResult
.unwrapErr();
10630 // We'll update selection after deleting the content nodes and nobody
10631 // refers selection until then. Therefore, we don't need to update
10633 createNewDivElementResult
.IgnoreCaretPointSuggestion();
10634 RefPtr
<Element
> newDivElement
= createNewDivElementResult
.UnwrapNewNode();
10635 MOZ_ASSERT(newDivElement
);
10636 // Delete anything that was in the list of nodes
10637 // XXX We don't need to remove items from the array.
10638 for (OwningNonNull
<nsIContent
>& curNode
: arrayOfContents
) {
10639 // MOZ_KnownLive because 'arrayOfContents' is guaranteed to keep it alive.
10640 nsresult rv
= DeleteNodeWithTransaction(MOZ_KnownLive(*curNode
));
10641 if (NS_FAILED(rv
)) {
10642 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
10646 // Don't restore the selection
10647 restoreSelectionLater
.Abort();
10648 nsresult rv
= CollapseSelectionToStartOf(*newDivElement
);
10649 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
10650 "EditorBase::CollapseSelectionToStartOf() failed");
10651 *aTargetElement
= std::move(newDivElement
);
10655 // `<div>` element to be positioned absolutely. This may have already
10656 // existed or newly created by this method.
10657 RefPtr
<Element
> targetDivElement
;
10658 // Newly created list element for moving selected list item elements into
10659 // targetDivElement. I.e., this is created in the `<div>` element.
10660 RefPtr
<Element
> createdListElement
;
10661 // If we handle a parent list item element, this is set to it. In such case,
10662 // we should handle its children again.
10663 RefPtr
<Element
> handledListItemElement
;
10664 for (OwningNonNull
<nsIContent
>& content
: arrayOfContents
) {
10665 // Here's where we actually figure out what to do.
10666 EditorDOMPoint
atContent(content
);
10667 if (NS_WARN_IF(!atContent
.IsSet())) {
10668 return NS_ERROR_FAILURE
; // XXX not continue??
10671 // Ignore all non-editable nodes. Leave them be.
10672 if (!EditorUtils::IsEditableContent(content
, EditorType::HTML
)) {
10676 // If current node is a child of a list element, we need another list
10677 // element in absolute-positioned `<div>` element to avoid non-selected
10678 // list items are moved into the `<div>` element.
10679 if (HTMLEditUtils::IsAnyListElement(atContent
.GetContainer())) {
10680 // If we cannot move current node to created list element, we need a
10681 // list element in the target `<div>` element for the destination.
10682 // Therefore, duplicate same list element into the target `<div>`
10684 nsIContent
* previousEditableContent
=
10686 ? HTMLEditUtils::GetPreviousSibling(
10687 content
, {WalkTreeOption::IgnoreNonEditableNode
})
10689 if (!createdListElement
||
10690 (previousEditableContent
&&
10691 previousEditableContent
!= createdListElement
)) {
10692 nsAtom
* ULOrOLOrDLTagName
=
10693 atContent
.GetContainer()->NodeInfo()->NameAtom();
10694 if (targetDivElement
) {
10695 // XXX Do we need to split the container? Since we'll append new
10696 // element at end of the <div> element.
10697 const SplitNodeResult splitNodeResult
=
10698 MaybeSplitAncestorsForInsertWithTransaction(
10699 MOZ_KnownLive(*ULOrOLOrDLTagName
), atContent
);
10700 if (splitNodeResult
.isOk()) {
10702 "HTMLEditor::MaybeSplitAncestorsForInsertWithTransaction() "
10704 return splitNodeResult
.unwrapErr();
10706 // We'll update selection after creating a list element below.
10707 // Therefore, we don't need to touch selection here.
10708 splitNodeResult
.IgnoreCaretPointSuggestion();
10710 // If we've not had a target <div> element yet, let's insert a <div>
10711 // element with splitting the ancestors.
10712 CreateElementResult createNewDivElementResult
=
10713 InsertElementWithSplittingAncestorsWithTransaction(
10714 *nsGkAtoms::div
, atContent
, BRElementNextToSplitPoint::Keep
,
10716 if (createNewDivElementResult
.isErr()) {
10719 "InsertElementWithSplittingAncestorsWithTransaction(nsGkAtoms::"
10721 return createNewDivElementResult
.unwrapErr();
10723 // We'll update selection after creating a list element below.
10724 // Therefor, we don't need to touch selection here.
10725 createNewDivElementResult
.IgnoreCaretPointSuggestion();
10726 MOZ_ASSERT(createNewDivElementResult
.GetNewNode());
10727 targetDivElement
= createNewDivElementResult
.UnwrapNewNode();
10729 CreateElementResult createNewListElementResult
= CreateAndInsertElement(
10730 WithTransaction::Yes
, MOZ_KnownLive(*ULOrOLOrDLTagName
),
10731 EditorDOMPoint::AtEndOf(targetDivElement
));
10732 if (createNewListElementResult
.isErr()) {
10734 "HTMLEditor::CreateAndInsertElement(WithTransaction::Yes) "
10736 return createNewListElementResult
.unwrapErr();
10738 nsresult rv
= createNewListElementResult
.SuggestCaretPointTo(
10739 *this, {SuggestCaret::OnlyIfHasSuggestion
,
10740 SuggestCaret::OnlyIfTransactionsAllowedToDoIt
,
10741 SuggestCaret::AndIgnoreTrivialError
});
10742 if (NS_FAILED(rv
)) {
10743 NS_WARNING("CreateElementResult::SuggestCaretPointTo() failed");
10746 NS_WARNING_ASSERTION(
10747 rv
!= NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR
,
10748 "CreateElementResult::SuggestCaretPointTo() failed, but ignored");
10749 createdListElement
= createNewListElementResult
.UnwrapNewNode();
10750 MOZ_ASSERT(createdListElement
);
10752 // Move current node (maybe, assumed as a list item element) into the
10753 // new list element in the target `<div>` element to be positioned
10755 // MOZ_KnownLive because 'arrayOfContents' is guaranteed to keep it alive.
10756 const MoveNodeResult moveNodeResult
= MoveNodeToEndWithTransaction(
10757 MOZ_KnownLive(content
), *createdListElement
);
10758 if (moveNodeResult
.isErr()) {
10759 NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed");
10760 return Err(moveNodeResult
.unwrapErr());
10762 nsresult rv
= moveNodeResult
.SuggestCaretPointTo(
10763 *this, {SuggestCaret::OnlyIfHasSuggestion
,
10764 SuggestCaret::OnlyIfTransactionsAllowedToDoIt
,
10765 SuggestCaret::AndIgnoreTrivialError
});
10766 if (NS_FAILED(rv
)) {
10767 NS_WARNING("MoveNodeResult::SuggestCaretPointTo() failed");
10770 NS_WARNING_ASSERTION(
10771 rv
!= NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR
,
10772 "MoveNodeResult::SuggestCaretPointTo() failed, but ignored");
10776 // If contents in a list item element is selected, we should move current
10777 // node into the target `<div>` element with the list item element itself
10778 // because we want to keep indent level of the contents.
10779 if (RefPtr
<Element
> listItemElement
=
10780 HTMLEditUtils::GetClosestAncestorListItemElement(content
,
10782 if (handledListItemElement
== listItemElement
) {
10783 // Current node has already been moved into the `<div>` element.
10786 // If we cannot move the list item element into created list element,
10787 // we need another list element in the target `<div>` element.
10788 nsIContent
* previousEditableContent
=
10790 ? HTMLEditUtils::GetPreviousSibling(
10791 *listItemElement
, {WalkTreeOption::IgnoreNonEditableNode
})
10793 if (!createdListElement
||
10794 (previousEditableContent
&&
10795 previousEditableContent
!= createdListElement
)) {
10796 EditorDOMPoint
atListItem(listItemElement
);
10797 if (NS_WARN_IF(!atListItem
.IsSet())) {
10798 return NS_ERROR_FAILURE
;
10800 // XXX If content is the listItemElement and not in a list element,
10801 // we duplicate wrong element into the target `<div>` element.
10802 nsAtom
* containerName
=
10803 atListItem
.GetContainer()->NodeInfo()->NameAtom();
10804 if (targetDivElement
) {
10805 // XXX Do we need to split the container? Since we'll append new
10806 // element at end of the <div> element.
10807 const SplitNodeResult splitNodeResult
=
10808 MaybeSplitAncestorsForInsertWithTransaction(
10809 MOZ_KnownLive(*containerName
), atListItem
);
10810 if (splitNodeResult
.isErr()) {
10812 "HTMLEditor::MaybeSplitAncestorsForInsertWithTransaction() "
10814 return splitNodeResult
.unwrapErr();
10816 // We'll update selection after creating a list element below.
10817 // Therefore, we don't need to touch selection here.
10818 splitNodeResult
.IgnoreCaretPointSuggestion();
10820 // If we've not had a target <div> element yet, let's insert a <div>
10821 // element with splitting the ancestors.
10822 CreateElementResult createNewDivElementResult
=
10823 InsertElementWithSplittingAncestorsWithTransaction(
10824 *nsGkAtoms::div
, atContent
, BRElementNextToSplitPoint::Keep
,
10826 if (createNewDivElementResult
.isErr()) {
10829 "InsertElementWithSplittingAncestorsWithTransaction("
10830 "nsGkAtoms::div) failed");
10831 return createNewDivElementResult
.unwrapErr();
10833 // We'll update selection after creating a list element below.
10834 // Therefore, we don't need to touch selection here.
10835 createNewDivElementResult
.IgnoreCaretPointSuggestion();
10836 MOZ_ASSERT(createNewDivElementResult
.GetNewNode());
10837 targetDivElement
= createNewDivElementResult
.UnwrapNewNode();
10839 // XXX So, createdListElement may be set to a non-list element.
10840 CreateElementResult createNewListElementResult
= CreateAndInsertElement(
10841 WithTransaction::Yes
, MOZ_KnownLive(*containerName
),
10842 EditorDOMPoint::AtEndOf(targetDivElement
));
10843 if (createNewListElementResult
.isErr()) {
10845 "HTMLEditor::CreateAndInsertElement(WithTransaction::Yes) "
10847 return createNewListElementResult
.unwrapErr();
10849 nsresult rv
= createNewListElementResult
.SuggestCaretPointTo(
10850 *this, {SuggestCaret::OnlyIfHasSuggestion
,
10851 SuggestCaret::OnlyIfTransactionsAllowedToDoIt
,
10852 SuggestCaret::AndIgnoreTrivialError
});
10853 if (NS_FAILED(rv
)) {
10854 NS_WARNING("CreateElementResult::SuggestCaretPointTo() failed");
10857 NS_WARNING_ASSERTION(
10858 rv
!= NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR
,
10859 "CreateElementResult::SuggestCaretPointTo() failed, but ignored");
10860 createdListElement
= createNewListElementResult
.UnwrapNewNode();
10861 MOZ_ASSERT(createdListElement
);
10863 // Move current list item element into the createdListElement (could be
10864 // non-list element due to the above bug) in a candidate `<div>` element
10865 // to be positioned absolutely.
10866 const MoveNodeResult moveListItemElementResult
=
10867 MoveNodeToEndWithTransaction(*listItemElement
, *createdListElement
);
10868 if (moveListItemElementResult
.isErr()) {
10869 NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed");
10870 return Err(moveListItemElementResult
.unwrapErr());
10872 nsresult rv
= moveListItemElementResult
.SuggestCaretPointTo(
10873 *this, {SuggestCaret::OnlyIfHasSuggestion
,
10874 SuggestCaret::OnlyIfTransactionsAllowedToDoIt
,
10875 SuggestCaret::AndIgnoreTrivialError
});
10876 if (NS_FAILED(rv
)) {
10877 NS_WARNING("MoveNodeResult::SuggestCaretPointTo() failed");
10880 NS_WARNING_ASSERTION(
10881 rv
!= NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR
,
10882 "MoveNodeResult::SuggestCaretPointTo() failed, but ignored");
10883 handledListItemElement
= std::move(listItemElement
);
10887 if (!targetDivElement
) {
10888 // If we meet a `<div>` element, use it as the absolute-position
10890 // XXX This looks odd. If there are 2 or more `<div>` elements are
10891 // selected, first found `<div>` element will have all other
10893 if (content
->IsHTMLElement(nsGkAtoms::div
)) {
10894 targetDivElement
= content
->AsElement();
10895 MOZ_ASSERT(!createdListElement
);
10896 MOZ_ASSERT(!handledListItemElement
);
10899 // Otherwise, create new `<div>` element to be positioned absolutely
10900 // and to contain all selected nodes.
10901 CreateElementResult createNewDivElementResult
=
10902 InsertElementWithSplittingAncestorsWithTransaction(
10903 *nsGkAtoms::div
, atContent
, BRElementNextToSplitPoint::Keep
,
10905 if (createNewDivElementResult
.isErr()) {
10907 "HTMLEditor::InsertElementWithSplittingAncestorsWithTransaction("
10908 "nsGkAtoms::div) failed");
10909 return createNewDivElementResult
.unwrapErr();
10911 nsresult rv
= createNewDivElementResult
.SuggestCaretPointTo(
10912 *this, {SuggestCaret::OnlyIfHasSuggestion
,
10913 SuggestCaret::OnlyIfTransactionsAllowedToDoIt
});
10914 if (NS_FAILED(rv
)) {
10915 NS_WARNING("CreateElementResult::SuggestCaretPointTo() failed");
10918 MOZ_ASSERT(createNewDivElementResult
.GetNewNode());
10919 targetDivElement
= createNewDivElementResult
.UnwrapNewNode();
10922 // MOZ_KnownLive because 'arrayOfContents' is guaranteed to keep it alive.
10923 const MoveNodeResult moveNodeResult
=
10924 MoveNodeToEndWithTransaction(MOZ_KnownLive(content
), *targetDivElement
);
10925 if (moveNodeResult
.isErr()) {
10926 NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed");
10927 return moveNodeResult
.unwrapErr();
10929 nsresult rv
= moveNodeResult
.SuggestCaretPointTo(
10930 *this, {SuggestCaret::OnlyIfHasSuggestion
,
10931 SuggestCaret::OnlyIfTransactionsAllowedToDoIt
,
10932 SuggestCaret::AndIgnoreTrivialError
});
10933 if (NS_FAILED(rv
)) {
10934 NS_WARNING("MoveNodeResult::SuggestCaretPointTo() failed");
10937 NS_WARNING_ASSERTION(
10938 rv
!= NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR
,
10939 "MoveNodeResult::SuggestCaretPointTo() failed, but ignored");
10940 // Forget createdListElement, if any
10941 createdListElement
= nullptr;
10943 *aTargetElement
= std::move(targetDivElement
);
10947 EditActionResult
HTMLEditor::SetSelectionToStaticAsSubAction() {
10948 MOZ_ASSERT(IsEditActionDataAvailable());
10950 AutoPlaceholderBatch
treatAsOneTransaction(
10951 *this, ScrollSelectionIntoView::Yes
, __FUNCTION__
);
10952 IgnoredErrorResult ignoredError
;
10953 AutoEditSubActionNotifier
startToHandleEditSubAction(
10954 *this, EditSubAction::eSetPositionToStatic
, nsIEditor::eNext
,
10956 if (NS_WARN_IF(ignoredError
.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED
))) {
10957 return EditActionResult(ignoredError
.StealNSResult());
10959 NS_WARNING_ASSERTION(
10960 !ignoredError
.Failed(),
10961 "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
10963 EditActionResult result
= CanHandleHTMLEditSubAction();
10964 if (result
.Failed() || result
.Canceled()) {
10965 NS_WARNING_ASSERTION(result
.Succeeded(),
10966 "HTMLEditor::CanHandleHTMLEditSubAction() failed");
10970 nsresult rv
= EnsureNoPaddingBRElementForEmptyEditor();
10971 if (NS_WARN_IF(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
10972 return EditActionResult(NS_ERROR_EDITOR_DESTROYED
);
10974 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
10975 "EditorBase::EnsureNoPaddingBRElementForEmptyEditor() "
10976 "failed, but ignored");
10978 if (NS_SUCCEEDED(rv
) && SelectionRef().IsCollapsed()) {
10979 nsresult rv
= EnsureCaretNotAfterInvisibleBRElement();
10980 if (NS_WARN_IF(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
10981 return EditActionResult(NS_ERROR_EDITOR_DESTROYED
);
10983 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
10984 "HTMLEditor::EnsureCaretNotAfterInvisibleBRElement() "
10985 "failed, but ignored");
10986 if (NS_SUCCEEDED(rv
)) {
10987 nsresult rv
= PrepareInlineStylesForCaret();
10988 if (NS_WARN_IF(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
10989 return EditActionResult(NS_ERROR_EDITOR_DESTROYED
);
10991 NS_WARNING_ASSERTION(
10993 "HTMLEditor::PrepareInlineStylesForCaret() failed, but ignored");
10997 RefPtr
<Element
> element
= GetAbsolutelyPositionedSelectionContainer();
10999 if (NS_WARN_IF(Destroyed())) {
11000 return EditActionHandled(NS_ERROR_EDITOR_DESTROYED
);
11003 "HTMLEditor::GetAbsolutelyPositionedSelectionContainer() returned "
11005 return EditActionHandled(NS_ERROR_FAILURE
);
11009 AutoSelectionRestorer
restoreSelectionLater(*this);
11011 nsresult rv
= SetPositionToAbsoluteOrStatic(*element
, false);
11012 if (NS_WARN_IF(Destroyed())) {
11013 return EditActionHandled(NS_ERROR_EDITOR_DESTROYED
);
11015 if (NS_FAILED(rv
)) {
11016 NS_WARNING("HTMLEditor::SetPositionToAbsoluteOrStatic() failed");
11017 return EditActionHandled(rv
);
11021 // Restoring Selection might cause destroying the HTML editor.
11022 return NS_WARN_IF(Destroyed()) ? EditActionHandled(NS_ERROR_EDITOR_DESTROYED
)
11023 : EditActionHandled(NS_OK
);
11026 EditActionResult
HTMLEditor::AddZIndexAsSubAction(int32_t aChange
) {
11027 MOZ_ASSERT(IsEditActionDataAvailable());
11029 AutoPlaceholderBatch
treatAsOneTransaction(
11030 *this, ScrollSelectionIntoView::Yes
, __FUNCTION__
);
11031 IgnoredErrorResult ignoredError
;
11032 AutoEditSubActionNotifier
startToHandleEditSubAction(
11034 aChange
< 0 ? EditSubAction::eDecreaseZIndex
11035 : EditSubAction::eIncreaseZIndex
,
11036 nsIEditor::eNext
, ignoredError
);
11037 if (NS_WARN_IF(ignoredError
.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED
))) {
11038 return EditActionResult(ignoredError
.StealNSResult());
11040 NS_WARNING_ASSERTION(
11041 !ignoredError
.Failed(),
11042 "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
11044 EditActionResult result
= CanHandleHTMLEditSubAction();
11045 if (result
.Failed() || result
.Canceled()) {
11046 NS_WARNING_ASSERTION(result
.Succeeded(),
11047 "HTMLEditor::CanHandleHTMLEditSubAction() failed");
11051 nsresult rv
= EnsureNoPaddingBRElementForEmptyEditor();
11052 if (NS_WARN_IF(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
11053 return EditActionHandled(NS_ERROR_EDITOR_DESTROYED
);
11055 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
11056 "EditorBase::EnsureNoPaddingBRElementForEmptyEditor() "
11057 "failed, but ignored");
11059 if (NS_SUCCEEDED(rv
) && SelectionRef().IsCollapsed()) {
11060 nsresult rv
= EnsureCaretNotAfterInvisibleBRElement();
11061 if (NS_WARN_IF(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
11062 return EditActionHandled(NS_ERROR_EDITOR_DESTROYED
);
11064 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
11065 "HTMLEditor::EnsureCaretNotAfterInvisibleBRElement() "
11066 "failed, but ignored");
11067 if (NS_SUCCEEDED(rv
)) {
11068 nsresult rv
= PrepareInlineStylesForCaret();
11069 if (NS_WARN_IF(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
11070 return EditActionHandled(NS_ERROR_EDITOR_DESTROYED
);
11072 NS_WARNING_ASSERTION(
11074 "HTMLEditor::PrepareInlineStylesForCaret() failed, but ignored");
11078 RefPtr
<Element
> absolutelyPositionedElement
=
11079 GetAbsolutelyPositionedSelectionContainer();
11080 if (!absolutelyPositionedElement
) {
11081 if (NS_WARN_IF(Destroyed())) {
11082 return EditActionHandled(NS_ERROR_EDITOR_DESTROYED
);
11085 "HTMLEditor::GetAbsolutelyPositionedSelectionContainer() returned "
11087 return EditActionHandled(NS_ERROR_FAILURE
);
11090 nsStyledElement
* absolutelyPositionedStyledElement
=
11091 nsStyledElement::FromNode(absolutelyPositionedElement
);
11092 if (NS_WARN_IF(!absolutelyPositionedStyledElement
)) {
11093 return EditActionHandled(NS_ERROR_FAILURE
);
11097 AutoSelectionRestorer
restoreSelectionLater(*this);
11099 // MOZ_KnownLive(*absolutelyPositionedStyledElement): It's
11100 // absolutelyPositionedElement whose type is RefPtr.
11101 Result
<int32_t, nsresult
> result
= AddZIndexWithTransaction(
11102 MOZ_KnownLive(*absolutelyPositionedStyledElement
), aChange
);
11103 if (result
.isErr()) {
11104 NS_WARNING("HTMLEditor::AddZIndexWithTransaction() failed");
11105 return EditActionHandled(result
.unwrapErr());
11109 // Restoring Selection might cause destroying the HTML editor.
11110 return NS_WARN_IF(Destroyed()) ? EditActionHandled(NS_ERROR_EDITOR_DESTROYED
)
11111 : EditActionHandled(NS_OK
);
11114 nsresult
HTMLEditor::OnDocumentModified() {
11115 if (mPendingDocumentModifiedRunner
) {
11116 return NS_OK
; // We've already posted same runnable into the queue.
11118 mPendingDocumentModifiedRunner
= NewRunnableMethod(
11119 "HTMLEditor::OnModifyDocument", this, &HTMLEditor::OnModifyDocument
);
11120 nsContentUtils::AddScriptRunner(do_AddRef(mPendingDocumentModifiedRunner
));
11121 // Be aware, if OnModifyDocument() may be called synchronously, the
11122 // editor might have been destroyed here.
11123 return NS_WARN_IF(Destroyed()) ? NS_ERROR_EDITOR_DESTROYED
: NS_OK
;
11126 } // namespace mozilla