Bug 1772588 [wpt PR 34302] - [wpt] Add test for block-in-inline offsetParent., a...
[gecko.git] / editor / libeditor / HTMLEditSubActionHandler.cpp
blobb64fc0e9acc6df9b8cb4eff7db31d716a5ee9e41
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"
9 #include <algorithm>
10 #include <utility>
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"
44 #include "nsAtom.h"
45 #include "nsCRT.h"
46 #include "nsCRTGlue.h"
47 #include "nsComponentManagerUtils.h"
48 #include "nsContentUtils.h"
49 #include "nsDebug.h"
50 #include "nsError.h"
51 #include "nsFrameSelection.h"
52 #include "nsGkAtoms.h"
53 #include "nsHTMLDocument.h"
54 #include "nsIContent.h"
55 #include "nsID.h"
56 #include "nsIFrame.h"
57 #include "nsINode.h"
58 #include "nsLiteralString.h"
59 #include "nsPrintfCString.h"
60 #include "nsRange.h"
61 #include "nsReadableUtils.h"
62 #include "nsString.h"
63 #include "nsStringFwd.h"
64 #include "nsStyledElement.h"
65 #include "nsTArray.h"
66 #include "nsTextNode.h"
67 #include "nsThreadUtils.h"
68 #include "nsUnicharUtils.h"
70 // Workaround for windows headers
71 #ifdef SetProp
72 # undef SetProp
73 #endif
75 class nsISupports;
77 namespace mozilla {
79 using namespace dom;
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:
108 return true;
109 default:
110 return false;
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();
181 if (NS_FAILED(rv)) {
182 NS_WARNING("EditorBase::InitEditorContentAndSelection() failed");
183 return rv;
186 Element* bodyOrDocumentElement = GetRoot();
187 if (NS_WARN_IF(!bodyOrDocumentElement && !GetDocument())) {
188 return NS_ERROR_FAILURE;
191 if (!bodyOrDocumentElement) {
192 return NS_OK;
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(
203 NS_SUCCEEDED(rv),
204 "HTMLEditor::InsertBRElementToEmptyListItemsAndTableCellsInRange() "
205 "failed, but ignored");
206 return NS_OK;
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);
224 return;
227 if (!mInitSucceeded) {
228 return; // We should do nothing if we're being initialized.
231 NS_WARNING_ASSERTION(
232 !aRv.Failed(),
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
240 // range.
241 TopLevelEditSubActionDataRef().mSelectedRange->StoreRange(
242 atCompositionStart, GetLastIMESelectionEndPoint<EditorRawDOMPoint>());
243 } else {
244 // Get the selection location
245 // XXX This may occur so that I think that we shouldn't throw exception
246 // in this case.
247 if (NS_WARN_IF(!SelectionRef().RangeCount())) {
248 aRv.Throw(NS_ERROR_UNEXPECTED);
249 return;
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;
267 break;
268 default:
269 cacheInlineStyles =
270 IsStyleCachePreservingSubAction(aTopLevelEditSubAction);
271 break;
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);
280 return;
282 nsresult rv = CacheInlineStyles(*containerContent);
283 if (NS_FAILED(rv)) {
284 NS_WARNING("HTMLEditor::CacheInlineStyles() failed");
285 aRv.Throw(rv);
286 return;
290 // Stabilize the document against contenteditable count changes
291 Document* document = GetDocument();
292 if (NS_WARN_IF(!document)) {
293 aRv.Throw(NS_ERROR_FAILURE);
294 return;
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);
305 return;
307 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
308 "HTMLEditor::EnsureSelectionInBodyOrDocumentElement() "
309 "failed, but ignored");
312 nsresult HTMLEditor::OnEndHandlingTopLevelEditSubAction() {
313 MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable());
315 nsresult rv;
316 while (true) {
317 if (NS_WARN_IF(Destroyed())) {
318 rv = NS_ERROR_EDITOR_DESTROYED;
319 break;
322 if (!mInitSucceeded) {
323 rv = NS_OK; // We should do nothing if we're being initialized.
324 break;
327 // Do all the tricky stuff
328 rv = OnEndHandlingTopLevelEditSubActionInternal();
329 NS_WARNING_ASSERTION(
330 NS_SUCCEEDED(rv),
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;
347 break;
349 if (document->GetEditingState() ==
350 Document::EditingState::eContentEditable) {
351 document->ChangeContentEditableCount(nullptr, -1);
354 break;
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);
363 return rv;
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:
380 return NS_OK;
381 default:
382 break;
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
405 // white-spaces.
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);
414 break;
416 default: {
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);
426 break;
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
440 // the deletion.
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(
454 newCaretPosition);
455 if (NS_FAILED(rv)) {
456 NS_WARNING(
457 "HTMLEditor::"
458 "InsertBRElementIfHardLineIsEmptyAndEndsWithBlockBoundary() "
459 "failed");
460 return rv;
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(
472 NS_SUCCEEDED(rv),
473 "HTMLEditor::InsertBRElementToEmptyListItemsAndTableCellsInRange()"
474 " failed, but ignored");
476 // merge any adjacent text nodes
477 switch (GetTopLevelEditSubAction()) {
478 case EditSubAction::eInsertText:
479 case EditSubAction::eInsertTextComingFromIME:
480 break;
481 default: {
482 nsresult rv = CollapseAdjacentTextNodes(
483 MOZ_KnownLive(*TopLevelEditSubActionDataRef().mChangedRange));
484 if (NS_WARN_IF(Destroyed())) {
485 return NS_ERROR_EDITOR_DESTROYED;
487 if (NS_FAILED(rv)) {
488 NS_WARNING("HTMLEditor::CollapseAdjacentTextNodes() failed");
489 return rv;
491 break;
495 // Clean up any empty nodes in the changed range unless they are inserted
496 // intentionally.
497 if (TopLevelEditSubActionDataRef().mNeedsToCleanUpEmptyElements) {
498 nsresult rv = RemoveEmptyNodesIn(
499 MOZ_KnownLive(*TopLevelEditSubActionDataRef().mChangedRange));
500 if (NS_FAILED(rv)) {
501 NS_WARNING("HTMLEditor::RemoveEmptyNodesIn() failed");
502 return rv;
506 // attempt to transform any unneeded nbsp's into spaces after doing various
507 // operations
508 switch (GetTopLevelEditSubAction()) {
509 case EditSubAction::eDeleteSelectedContent:
510 if (TopLevelEditSubActionDataRef().mDidNormalizeWhitespaces) {
511 break;
513 [[fallthrough]];
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
525 // expensive.
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(),
536 EditorType::HTML)) {
537 AutoTrackDOMPoint trackPointToAdjust(RangeUpdaterRef(),
538 &pointToAdjust);
539 nsresult rv =
540 WhiteSpaceVisibilityKeeper::NormalizeVisibleWhiteSpacesAt(
541 *this, pointToAdjust);
542 if (NS_FAILED(rv)) {
543 NS_WARNING(
544 "WhiteSpaceVisibilityKeeper::NormalizeVisibleWhiteSpacesAt() "
545 "failed");
546 return rv;
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
553 // should do?
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(),
563 EditorType::HTML)) {
564 AutoTrackDOMPoint trackPointToAdjust(RangeUpdaterRef(),
565 &pointToAdjust);
566 AutoTrackDOMPoint trackStartPoint(RangeUpdaterRef(), &atStart);
567 nsresult rv =
568 WhiteSpaceVisibilityKeeper::NormalizeVisibleWhiteSpacesAt(
569 *this, atStart);
570 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
571 return NS_ERROR_EDITOR_DESTROYED;
573 NS_WARNING_ASSERTION(
574 NS_SUCCEEDED(rv),
575 "WhiteSpaceVisibilityKeeper::NormalizeVisibleWhiteSpacesAt() "
576 "failed, but ignored");
578 // we only need to handle old selection endpoint if it was different
579 // from start
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(),
586 EditorType::HTML)) {
587 nsresult rv =
588 WhiteSpaceVisibilityKeeper::NormalizeVisibleWhiteSpacesAt(*this,
589 atEnd);
590 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
591 return NS_ERROR_EDITOR_DESTROYED;
593 NS_WARNING_ASSERTION(
594 NS_SUCCEEDED(rv),
595 "WhiteSpaceVisibilityKeeper::NormalizeVisibleWhiteSpacesAt() "
596 "failed, but ignored");
598 break;
600 default:
601 break;
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(
613 NS_SUCCEEDED(rv),
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
620 // did it.
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());
637 if (NS_FAILED(rv)) {
638 NS_WARNING(
639 "HTMLEditor::AdjustCaretPositionAndEnsurePaddingBRElement() "
640 "failed");
641 return rv;
643 break;
644 default:
645 break;
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;
656 break;
657 default:
658 reapplyCachedStyle =
659 IsStyleCachePreservingSubAction(GetTopLevelEditSubAction());
660 break;
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)) {
674 break;
676 mostDistantEmptyInlineAncestor = ancestor;
678 if (mostDistantEmptyInlineAncestor) {
679 nsresult rv =
680 DeleteNodeWithTransaction(*mostDistantEmptyInlineAncestor);
681 if (NS_FAILED(rv)) {
682 NS_WARNING(
683 "EditorBase::DeleteNodeWithTransaction() failed at deleting "
684 "empty inline ancestors");
685 return rv;
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);
706 if (NS_FAILED(rv)) {
707 NS_WARNING("EditorBase::HandleInlineSpellCheck() failed");
708 return rv;
711 // detect empty doc
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();
716 if (NS_FAILED(rv)) {
717 NS_WARNING(
718 "EditorBase::MaybeCreatePaddingBRElementForEmptyEditor() failed");
719 return rv;
722 // adjust selection HINT if needed
723 if (!TopLevelEditSubActionDataRef().mDidExplicitlySetInterLine &&
724 SelectionRef().IsCollapsed()) {
725 SetSelectionInterlinePosition();
728 return NS_OK;
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) {
773 NS_WARNING(
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()) {
812 return NS_OK;
815 Element* editingHost = ComputeEditingHost();
816 if (!editingHost) {
817 NS_WARNING(
818 "HTMLEditor::EnsureCaretNotAfterInvisibleBRElement() did nothing "
819 "because of no editing host");
820 return NS_OK;
823 nsIContent* previousBRElement =
824 HTMLEditUtils::GetPreviousContent(atSelectionStart, {}, editingHost);
825 if (!previousBRElement || !previousBRElement->IsHTMLElement(nsGkAtoms::br) ||
826 !previousBRElement->GetParent() ||
827 !EditorUtils::IsEditableContent(*previousBRElement->GetParent(),
828 EditorType::HTML) ||
829 !HTMLEditUtils::IsInvisibleBRElement(*previousBRElement)) {
830 return NS_OK;
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) {
843 return NS_OK;
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");
854 return rv;
857 nsresult HTMLEditor::MaybeCreatePaddingBRElementForEmptyEditor() {
858 MOZ_ASSERT(IsEditActionDataAvailable());
860 if (mPaddingBRElementForEmptyEditor) {
861 return NS_OK;
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();
877 if (!rootElement) {
878 return NS_OK;
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) ||
890 !isRootEditable ||
891 EditorUtils::IsEditableContent(*rootChild, editorType) ||
892 HTMLEditUtils::IsBlockElement(*rootChild)) {
893 return NS_OK;
897 // Skip adding the padding <br> element for empty editor if body
898 // is read-only.
899 if (IsHTMLEditor() && !HTMLEditUtils::IsSimplyEditableNode(*rootElement)) {
900 return NS_OK;
903 // Create a br.
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();
927 // Set selection.
928 insertBRElementResult.IgnoreCaretPointSuggestion();
929 nsresult rv = CollapseSelectionToStartOf(*rootElement);
930 if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) {
931 NS_WARNING(
932 "EditorBase::CollapseSelectionToStartOf() caused destroying the "
933 "editor");
934 return NS_ERROR_EDITOR_DESTROYED;
936 NS_WARNING_ASSERTION(
937 NS_SUCCEEDED(rv),
938 "EditorBase::CollapseSelectionToStartOf() failed, but ignored");
939 return NS_OK;
942 nsresult HTMLEditor::EnsureNoPaddingBRElementForEmptyEditor() {
943 MOZ_ASSERT(IsEditActionDataAvailable());
945 if (!mPaddingBRElementForEmptyEditor) {
946 return NS_OK;
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");
957 return rv;
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);
976 } else {
977 mPaddingBRElementForEmptyEditor = nullptr;
979 return NS_OK;
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();
995 if (NS_FAILED(rv)) {
996 NS_WARNING("HTMLEditor::ReapplyCachedStyles() failed");
997 return rv;
999 break;
1001 default:
1002 break;
1005 // For most actions we want to clear the cached styles, but there are
1006 // exceptions
1007 if (!IsStyleCachePreservingSubAction(GetTopLevelEditSubAction())) {
1008 TopLevelEditSubActionDataRef().mCachedInlineStyles->Clear();
1010 return NS_OK;
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");
1026 return result;
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) {
1035 nsresult rv =
1036 DeleteSelectionAsSubAction(nsIEditor::eNone, nsIEditor::eNoStrip);
1037 if (NS_FAILED(rv)) {
1038 NS_WARNING(
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(
1067 NS_SUCCEEDED(rv),
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
1104 // the DOM tree.
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);
1191 int32_t pos = 0;
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()) {
1201 while (pos != -1 &&
1202 pos < AssertedCast<int32_t>(aInsertionString.Length())) {
1203 int32_t oldPos = pos;
1204 int32_t subStrLen;
1205 pos = aInsertionString.FindChar(nsCRT::LF, oldPos);
1207 if (pos != -1) {
1208 subStrLen = pos - oldPos;
1209 // if first char is newline, then use just it
1210 if (!subStrLen) {
1211 subStrLen = 1;
1213 } else {
1214 subStrLen = aInsertionString.Length() - oldPos;
1215 pos = aInsertionString.Length();
1218 nsDependentSubstring subStr(aInsertionString, oldPos, subStrLen);
1220 // is it a return?
1221 if (subStr.Equals(newlineStr)) {
1222 CreateElementResult insertBRElementResult =
1223 InsertBRElement(WithTransaction::Yes, currentPoint);
1224 if (insertBRElementResult.isErr()) {
1225 NS_WARNING(
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());
1235 pos++;
1236 RefPtr<Element> brElement = insertBRElementResult.UnwrapNewNode();
1237 if (brElement->GetNextSibling()) {
1238 pointToInsert.Set(brElement->GetNextSibling());
1239 } else {
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");
1252 } else {
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();
1263 } else {
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().
1273 while (pos != -1 &&
1274 pos < AssertedCast<int32_t>(insertionString.Length())) {
1275 int32_t oldPos = pos;
1276 int32_t subStrLen;
1277 pos = insertionString.FindCharInSet(specialChars, oldPos);
1279 if (pos != -1) {
1280 subStrLen = pos - oldPos;
1281 // if first char is newline, then use just it
1282 if (!subStrLen) {
1283 subStrLen = 1;
1285 } else {
1286 subStrLen = insertionString.Length() - oldPos;
1287 pos = insertionString.Length();
1290 nsDependentSubstring subStr(insertionString, oldPos, subStrLen);
1292 // is it a tab?
1293 if (subStr.Equals(tabStr)) {
1294 Result<EditorDOMPoint, nsresult> insertTextResult =
1295 WhiteSpaceVisibilityKeeper::InsertText(*this, spacesStr,
1296 currentPoint);
1297 if (MOZ_UNLIKELY(insertTextResult.isErr())) {
1298 NS_WARNING("WhiteSpaceVisibilityKeeper::InsertText() failed");
1299 return EditActionHandled(insertTextResult.unwrapErr());
1301 pos++;
1302 MOZ_ASSERT(insertTextResult.inspect().IsSet());
1303 currentPoint = insertTextResult.inspect();
1304 pointToInsert = insertTextResult.unwrap();
1306 // is it a return?
1307 else if (subStr.Equals(newlineStr)) {
1308 CreateElementResult insertBRElementResult =
1309 WhiteSpaceVisibilityKeeper::InsertBRElement(*this, currentPoint,
1310 *editingHost);
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");
1329 pos++;
1330 RefPtr<Element> newBRElement = insertBRElementResult.UnwrapNewNode();
1331 MOZ_DIAGNOSTIC_ASSERT(newBRElement);
1332 if (newBRElement->GetNextSibling()) {
1333 pointToInsert.Set(newBRElement->GetNextSibling());
1334 } else {
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");
1347 } else {
1348 Result<EditorDOMPoint, nsresult> insertTextResult =
1349 WhiteSpaceVisibilityKeeper::InsertText(*this, subStr,
1350 currentPoint);
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)) {
1369 NS_WARNING(
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");
1406 return result.Rv();
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,
1413 __FUNCTION__);
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
1418 // is better.
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()) {
1433 nsresult rv =
1434 DeleteSelectionAsSubAction(nsIEditor::eNone, nsIEditor::eStrip);
1435 if (NS_FAILED(rv)) {
1436 NS_WARNING(
1437 "EditorBase::DeleteSelectionAsSubAction(eNone, eStrip) failed");
1438 return rv;
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,
1462 *editingHost)) {
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());
1473 return rv;
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(
1498 NS_SUCCEEDED(rv),
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");
1523 return rv;
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");
1535 return result;
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,
1542 __FUNCTION__);
1544 IgnoredErrorResult ignoredError;
1545 AutoEditSubActionNotifier startToHandleEditSubAction(
1546 *this, EditSubAction::eInsertParagraphSeparator, nsIEditor::eNext,
1547 ignoredError);
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()) {
1559 nsresult rv =
1560 DeleteSelectionAsSubAction(nsIEditor::eNone, nsIEditor::eStrip);
1561 if (NS_FAILED(rv)) {
1562 NS_WARNING(
1563 "EditorBase::DeleteSelectionAsSubAction(eNone, eStrip) failed");
1564 return 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(
1590 NS_SUCCEEDED(rv),
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,
1612 *editingHost);
1613 if (MOZ_UNLIKELY(atNewBRElementOrError.isErr())) {
1614 NS_WARNING(
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
1656 // does it.
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)
1673 : nullptr;
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)) {
1687 insertLineBreak =
1688 separator == ParagraphSeparator::br ||
1689 !HTMLEditUtils::CanElementContainParagraph(*editingHost) ||
1690 HTMLEditUtils::ShouldInsertLinefeedCharacter(atStartOfSelection,
1691 *editingHost);
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.
1701 else {
1702 insertLineBreak = true;
1703 for (const Element* editableBlockAncestor = editableBlockElement;
1704 editableBlockAncestor && insertLineBreak;
1705 editableBlockAncestor = HTMLEditUtils::GetAncestorElement(
1706 *editableBlockAncestor,
1707 HTMLEditUtils::ClosestEditableBlockElement)) {
1708 insertLineBreak =
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,
1720 *editingHost)) {
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,
1838 editingHost);
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);
1849 NS_WARNING(
1850 "HTMLEditor::HandleInsertParagraphInListItemElement() failed, but "
1851 "ignored");
1852 return EditActionHandled();
1854 nsresult rv = CollapseSelectionTo(pointToPutCaretOrError.unwrap());
1855 if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) {
1856 NS_WARNING(
1857 "EditorBase::CollapseSelectionTo() caused destroying the editor");
1858 return EditActionHandled(NS_ERROR_EDITOR_DESTROYED);
1860 NS_WARNING_ASSERTION(
1861 NS_SUCCEEDED(rv),
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);
1876 NS_WARNING(
1877 "HTMLEditor::HandleInsertParagraphInHeadingElement() failed, but "
1878 "ignored");
1879 return EditActionHandled();
1881 nsresult rv = CollapseSelectionTo(pointToPutCaretOrError.unwrap());
1882 if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) {
1883 NS_WARNING(
1884 "EditorBase::CollapseSelectionTo() caused destroying the editor");
1885 return EditActionHandled(NS_ERROR_EDITOR_DESTROYED);
1887 NS_WARNING_ASSERTION(
1888 NS_SUCCEEDED(rv),
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,
1904 nsGkAtoms::div))) {
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");
1911 return result;
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();
1918 return result;
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 "
1924 "action instead");
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();
1963 } else {
1964 EditorDOMPoint pointToBreak(aPointToBreak);
1965 WSRunScanner wsRunScanner(&aEditingHost, pointToBreak);
1966 WSScanResult backwardScanResult =
1967 wsRunScanner.ScanPreviousVisibleNodeOrBlockBoundaryFrom(pointToBreak);
1968 if (MOZ_UNLIKELY(backwardScanResult.Failed())) {
1969 NS_WARNING(
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())) {
1977 NS_WARNING(
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());
1986 if (linkNode) {
1987 const SplitNodeResult splitLinkNodeResult = SplitNodeDeepWithTransaction(
1988 *linkNode, pointToBreak, SplitAtEdges::eDoNotCreateEmptyContainer);
1989 if (splitLinkNodeResult.isErr()) {
1990 NS_WARNING(
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,
2013 aEditingHost);
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
2036 // between blocks.
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,
2045 afterBRElement);
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()),
2062 afterBRElement);
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");
2117 return rv;
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
2133 // here.
2134 if (!pointToInsert.IsInTextNode() &&
2135 !HTMLEditUtils::CanNodeContain(*pointToInsert.ContainerAsContent(),
2136 *nsGkAtoms::textTagName)) {
2137 NS_WARNING(
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(),
2156 &pointToInsert);
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(),
2178 *nsGkAtoms::br)) {
2179 auto newCaretPosition = caretAfterInsert.To<EditorDOMPoint>();
2181 AutoTrackDOMPoint trackingInsertedPosition(RangeUpdaterRef(),
2182 &pointToInsert);
2183 AutoTrackDOMPoint trackingNewCaretPosition(RangeUpdaterRef(),
2184 &newCaretPosition);
2185 CreateElementResult insertBRElementResult =
2186 InsertBRElement(WithTransaction::Yes, newCaretPosition);
2187 if (insertBRElementResult.isErr()) {
2188 NS_WARNING(
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(
2211 pointToInsert))) {
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");
2233 return rv;
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 "
2244 "for you?");
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,
2259 pointToSplit);
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())) {
2269 pointToSplit =
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()) {
2281 NS_WARNING(
2282 "HTMLEditor::SplitNodeDeepWithTransaction(aMailCiteElement, "
2283 "SplitAtEdges::eDoNotCreateEmptyContainer) failed");
2284 return splitResult;
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);
2293 return splitResult;
2294 }();
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
2307 // user saw one.
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
2357 // expects.
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);
2369 if (MOZ_UNLIKELY(
2370 backwardScanFromPointToCreateNewBRElementResult.Failed())) {
2371 NS_WARNING(
2372 "WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary() "
2373 "failed");
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(
2384 &aEditingHost,
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());
2407 return NS_OK;
2408 }();
2410 if (NS_FAILED(rvOfInsertingBRElement)) {
2411 NS_WARNING(
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");
2424 return Err(rv);
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");
2434 return Err(rv);
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));
2482 // static
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());
2498 if (aLength == 1) {
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);
2506 return;
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);
2512 return;
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);
2519 return;
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.
2524 aResult.Assign(
2525 aPreviousCharPointData.Type() == CharPointType::ASCIIWhiteSpace ||
2526 aNextCharPointData.Type() == CharPointType::ASCIIWhiteSpace
2527 ? HTMLEditUtils::kNBSP
2528 : HTMLEditUtils::kSpace);
2529 return;
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.
2542 if (appendNBSP) {
2543 *lastChar = HTMLEditUtils::kNBSP;
2544 return;
2547 // If next char point is end of text node, an ASCII white-space or
2548 // preformatted linefeed, we need to put an NBSP.
2549 *lastChar =
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,
2576 aEndToDelete);
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
2611 : 0);
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());
2621 } else {
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()) {
2649 lengthInEndNode =
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,
2672 nextCharPointData);
2673 } else {
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,
2679 nextCharPointData);
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
2691 // we do.
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
2717 // the given range.
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());
2738 } else {
2739 newCaretPosition.Set(aEndToDelete.ContainerAsText(), 0u);
2742 // Then, modify the text nodes in the range.
2743 while (true) {
2744 AutoTrackDOMPoint trackingNewCaretPosition(RangeUpdaterRef(),
2745 &newCaretPosition);
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");
2766 return Err(rv);
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.
2785 startToDelete =
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()
2801 ? endToDelete
2802 : EditorDOMPointInText(endToDelete.ContainerAsText(), 0);
2803 if (startToDelete != endToDeleteExceptReplaceRange) {
2804 nsresult rv = DeleteTextAndTextNodesWithTransaction(
2805 startToDelete, endToDeleteExceptReplaceRange, aTreatEmptyTextNodes);
2806 if (NS_FAILED(rv)) {
2807 NS_WARNING(
2808 "HTMLEditor::DeleteTextAndTextNodesWithTransaction() failed");
2809 return Err(rv);
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
2830 // last text node.
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");
2840 return Err(rv);
2842 break;
2845 if (!newCaretPosition.IsSetAndValid() ||
2846 !newCaretPosition.GetContainer()->IsInComposedDoc()) {
2847 NS_WARNING(
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(),
2859 HTMLEditUtils::
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)) {
2868 newCaretPosition =
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(
2878 newCaretPosition,
2879 *editableBlockElementOrInlineEditingHost,
2880 {LeafNodeType::LeafNodeOrNonEditableNode},
2881 editingHost)) {
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
2892 // range.
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(),
2903 &newCaretPosition);
2904 nsresult rv = InsertBRElementIfHardLineIsEmptyAndEndsWithBlockBoundary(
2905 newCaretPosition);
2906 if (NS_FAILED(rv)) {
2907 NS_WARNING(
2908 "HTMLEditor::"
2909 "InsertBRElementIfHardLineIsEmptyAndEndsWithBlockBoundary() failed");
2910 return Err(rv);
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()) {
2926 return NS_OK;
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())) {
2932 return NS_OK;
2935 WSRunScanner wsRunScanner(ComputeEditingHost(), aPointToInsert);
2936 // If the point is not start of a hard line, we don't need to put a `<br>`
2937 // element here.
2938 if (!wsRunScanner.StartsFromHardLineBreak()) {
2939 return NS_OK;
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()) {
2944 return NS_OK;
2947 // If we cannot insert a `<br>` element here, do nothing.
2948 if (!HTMLEditUtils::CanNodeContain(*aPointToInsert.GetContainer(),
2949 *nsGkAtoms::br)) {
2950 return NS_OK;
2953 CreateElementResult insertBRElementResult = InsertBRElement(
2954 WithTransaction::Yes, aPointToInsert, nsIEditor::ePrevious);
2955 if (insertBRElementResult.isErr()) {
2956 NS_WARNING(
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");
2963 return rv;
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");
2984 return result;
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(
3005 *this,
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(
3040 NS_SUCCEEDED(rv),
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;
3058 } else {
3059 NS_WARNING(
3060 "aListElementOrListItemElementTagName was neither list element name "
3061 "nor "
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
3068 // range.
3069 if (!SelectionRef().IsCollapsed()) {
3070 nsresult rv = MaybeExtendSelectionToHardLineEdgesForBlockEditAction();
3071 if (NS_FAILED(rv)) {
3072 NS_WARNING(
3073 "HTMLEditor::MaybeExtendSelectionToHardLineEdgesForBlockEditAction() "
3074 "failed");
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");
3090 return result;
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()
3111 : nullptr;
3112 if (parentListElement) {
3113 arrayOfContents.AppendElement(
3114 OwningNonNull<nsIContent>(*parentListElement));
3115 } else {
3116 AutoTransactionsConserveSelection dontChangeMySelection(*this);
3117 nsresult rv =
3118 SplitInlinesAndCollectEditTargetNodesInExtendedSelectionRanges(
3119 arrayOfContents, EditSubAction::eCreateOrChangeList,
3120 CollectNonEditableNodes::No);
3121 if (NS_FAILED(rv)) {
3122 NS_WARNING(
3123 "HTMLEditor::"
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;
3137 break;
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
3145 if (bOnlyBreaks) {
3146 for (auto& content : arrayOfContents) {
3147 // MOZ_KnownLive because 'arrayOfContents' is guaranteed to
3148 // keep it alive.
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()) {
3191 NS_WARNING(
3192 nsPrintfCString(
3193 "HTMLEditor::CreateAndInsertElement(%s) failed",
3194 ToString(withTransaction).c_str())
3195 .get());
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);
3208 return NS_OK;
3210 if (createNewListElementResult.isErr()) {
3211 NS_WARNING(
3212 nsPrintfCString(
3213 "HTMLEditor::InsertElementWithSplittingAncestorsWithTransaction("
3214 "%s) failed",
3215 nsAtomCString(&aListElementTagName).get())
3216 .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);
3246 } else {
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.
3265 if (curList &&
3266 HTMLEditUtils::GetInclusiveAncestorAnyTableElement(*curList) !=
3267 HTMLEditUtils::GetInclusiveAncestorAnyTableElement(content)) {
3268 curList = nullptr;
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;
3285 continue;
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())) {
3321 NS_WARNING(
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;
3335 continue;
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;
3349 continue;
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()) {
3383 NS_WARNING(
3384 "HTMLEditor::CreateAndInsertElement(WithTransaction::Yes) "
3385 "failed");
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");
3442 } else {
3443 // If we've not met a list element, set current list element to the
3444 // parent of current list item element.
3445 if (!curList) {
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)) {
3510 NS_WARNING(
3511 "EditorBase::SetAttributeWithTransaction(nsGkAtoms::type) "
3512 "failed");
3513 return EditActionResult(rv);
3515 continue;
3518 // Otherwise, remove list type attribute if there is.
3519 if (!element->HasAttr(nsGkAtoms::type)) {
3520 continue;
3522 nsresult rv = RemoveAttributeWithTransaction(MOZ_KnownLive(*element),
3523 *nsGkAtoms::type);
3524 if (NS_WARN_IF(Destroyed())) {
3525 return EditActionResult(NS_ERROR_EDITOR_DESTROYED);
3527 if (NS_FAILED(rv)) {
3528 NS_WARNING(
3529 "EditorBase::RemoveAttributeWithTransaction(nsGkAtoms::type) "
3530 "failed");
3531 return EditActionResult(rv);
3533 continue;
3536 MOZ_ASSERT(!HTMLEditUtils::IsAnyListElement(content) &&
3537 !HTMLEditUtils::IsListItem(content));
3539 // If current node is a `<div>` element, replace it in the array with
3540 // its children.
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();
3564 continue;
3567 // If we've not met a list element, create a list element and make it
3568 // current list element.
3569 if (!curList) {
3570 prevListItem = nullptr;
3571 CreateElementResult createNewListElementResult =
3572 InsertElementWithSplittingAncestorsWithTransaction(
3573 aListElementTagName, atContent, BRElementNextToSplitPoint::Keep,
3574 *editingHost);
3575 if (createNewListElementResult.isErr()) {
3576 NS_WARNING(
3577 nsPrintfCString(
3578 "HTMLEditor::"
3579 "InsertElementWithSplittingAncestorsWithTransaction(%s) failed",
3580 nsAtomCString(&aListElementTagName).get())
3581 .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.
3598 atContent.Clear();
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");
3621 continue;
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??
3660 continue;
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;
3678 } else {
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");
3711 return result.Rv();
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)) {
3729 NS_WARNING(
3730 "HTMLEditor::MaybeExtendSelectionToHardLineEdgesForBlockEditAction() "
3731 "failed");
3732 return rv;
3736 AutoSelectionRestorer restoreSelectionLater(*this);
3738 AutoTArray<OwningNonNull<nsIContent>, 64> arrayOfContents;
3740 AutoTransactionsConserveSelection dontChangeMySelection(*this);
3741 nsresult rv =
3742 SplitInlinesAndCollectEditTargetNodesInExtendedSelectionRanges(
3743 arrayOfContents, EditSubAction::eCreateOrChangeList,
3744 CollectNonEditableNodes::No);
3745 if (NS_FAILED(rv)) {
3746 NS_WARNING(
3747 "HTMLEditor::"
3748 "SplitInlinesAndCollectEditTargetNodesInExtendedSelectionRanges("
3749 "eCreateOrChangeList, CollectNonEditableNodes::No) failed");
3750 return rv;
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)) {
3773 NS_WARNING(
3774 "HTMLEditor::LiftUpListItemElement(LiftUpFromAllParentListElements:"
3775 ":Yes) failed");
3776 return rv;
3778 continue;
3780 if (HTMLEditUtils::IsAnyListElement(content)) {
3781 // node is a list, move list items out
3782 nsresult rv =
3783 DestroyListStructureRecursively(MOZ_KnownLive(*content->AsElement()));
3784 if (NS_FAILED(rv)) {
3785 NS_WARNING("HTMLEditor::DestroyListStructureRecursively() failed");
3786 return rv;
3788 continue;
3791 return NS_OK;
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)) {
3805 NS_WARNING(
3806 "HTMLEditor::MaybeExtendSelectionToHardLineEdgesForBlockEditAction() "
3807 "failed");
3808 return rv;
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)) {
3820 NS_WARNING(
3821 "HTMLEditor::"
3822 "SplitInlinesAndCollectEditTargetNodesInExtendedSelectionRanges("
3823 "eCreateOrRemoveBlock, CollectNonEditableNodes::Yes) failed");
3824 return rv;
3827 // If there is no visible and editable nodes in the edit targets, make an
3828 // empty block.
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()) {
3839 NS_WARNING(
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) {
3850 NS_WARNING(
3851 "HTMLEditor::FormatBlockContainerWithTransaction() couldn't find "
3852 "block parent");
3853 return NS_ERROR_FAILURE;
3855 if (!HTMLEditUtils::IsFormatNode(editableBlockElement)) {
3856 return NS_OK;
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},
3864 editingHost)) {
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");
3870 return rv;
3874 // Do the splits!
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");
3898 return rv;
3901 // We are making a block. Consume a br, if needed.
3902 if (nsCOMPtr<nsIContent> maybeBRContent = HTMLEditUtils::GetNextContent(
3903 pointToInsertBlock,
3904 {WalkTreeOption::IgnoreNonEditableNode,
3905 WalkTreeOption::StopAtBlockBoundary},
3906 editingHost)) {
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");
3912 return rv;
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,
3922 *editingHost);
3923 if (createNewBlockElementResult.isErr()) {
3924 NS_WARNING(
3925 nsPrintfCString(
3926 "HTMLEditor::InsertElementWithSplittingAncestorsWithTransaction("
3927 "%s) failed",
3928 nsAtomCString(&blockType).get())
3929 .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
3945 // keep it alive.
3946 nsresult rv = DeleteNodeWithTransaction(MOZ_KnownLive(*content));
3947 if (NS_FAILED(rv)) {
3948 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
3949 return rv;
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");
3962 return rv;
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
3966 // differently.
3967 if (&blockType == nsGkAtoms::blockquote) {
3968 nsresult rv = MoveNodesIntoNewBlockquoteElement(arrayOfContents);
3969 NS_WARNING_ASSERTION(
3970 NS_SUCCEEDED(rv),
3971 "HTMLEditor::MoveNodesIntoNewBlockquoteElement() failed");
3972 return rv;
3974 if (&blockType == nsGkAtoms::normal || &blockType == nsGkAtoms::_empty) {
3975 nsresult rv = RemoveBlockContainerElements(arrayOfContents);
3976 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
3977 "HTMLEditor::RemoveBlockContainerElements() failed");
3978 return rv;
3980 rv = CreateOrChangeBlockContainerElement(arrayOfContents, blockType);
3981 NS_WARNING_ASSERTION(
3982 NS_SUCCEEDED(rv),
3983 "HTMLEditor::CreateOrChangeBlockContainerElement() failed");
3984 return rv;
3987 nsresult HTMLEditor::MaybeInsertPaddingBRElementForEmptyLastLineAtSelection() {
3988 MOZ_ASSERT(IsEditActionDataAvailable());
3989 MOZ_ASSERT(!IsSelectionRangeContainerNotContent());
3991 if (!SelectionRef().IsCollapsed()) {
3992 return NS_OK;
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()) {
4004 return NS_OK;
4006 OwningNonNull<Element> startContainerElement =
4007 *atStartOfSelection.Container()->AsElement();
4008 nsresult rv =
4009 InsertPaddingBRElementForEmptyLastLineIfNeeded(startContainerElement);
4010 NS_WARNING_ASSERTION(
4011 NS_SUCCEEDED(rv),
4012 "HTMLEditor::InsertPaddingBRElementForEmptyLastLineIfNeeded() failed");
4013 return rv;
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");
4035 return result;
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");
4047 return result;
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(
4057 NS_SUCCEEDED(rv),
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");
4100 return rv;
4102 NS_WARNING_ASSERTION(
4103 rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
4104 "MoveNodeResult::SuggestCaretPointTo() failed, but ignored");
4105 return NS_OK;
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");
4133 return rv;
4135 NS_WARNING_ASSERTION(
4136 rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
4137 "MoveNodeResult::SuggestCaretPointTo() failed, but ignored");
4138 return NS_OK;
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})
4148 : nullptr;
4149 if (!*aCurList ||
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()) {
4158 NS_WARNING(
4159 nsPrintfCString(
4160 "HTMLEditor::InsertElementWithSplittingAncestorsWithTransaction("
4161 "%s) failed",
4162 nsAtomCString(containerName).get())
4163 .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");
4171 return rv;
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");
4194 return rv;
4196 NS_WARNING_ASSERTION(
4197 rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
4198 "MoveNodeResult::SuggestCaretPointTo() failed, but ignored");
4199 return NS_OK;
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(
4228 NS_SUCCEEDED(rv),
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)) {
4257 NS_WARNING(
4258 "HTMLEditor::MaybeExtendSelectionToHardLineEdgesForBlockEditAction() "
4259 "failed");
4260 return rv;
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(
4272 NS_SUCCEEDED(rv),
4273 "HTMLEditor::HandleCSSIndentAtSelectionInternal() failed");
4274 return rv;
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()) {
4309 nsresult rv =
4310 SplitInlinesAndCollectEditTargetNodesInExtendedSelectionRanges(
4311 arrayOfContents, EditSubAction::eIndent,
4312 CollectNonEditableNodes::Yes);
4313 if (NS_FAILED(rv)) {
4314 NS_WARNING(
4315 "HTMLEditor::"
4316 "SplitInlinesAndCollectEditTargetNodesInExtendedSelectionRanges("
4317 "eIndent, CollectNonEditableNodes::Yes) failed");
4318 return rv;
4322 // If there is no visible and editable nodes in the edit targets, make an
4323 // empty block.
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()) {
4343 NS_WARNING(
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
4367 // keep it alive.
4368 nsresult rv = DeleteNodeWithTransaction(MOZ_KnownLive(*content));
4369 if (NS_FAILED(rv)) {
4370 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
4371 return rv;
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");
4381 return rv;
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())) {
4391 continue;
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)) {
4397 continue;
4400 if (HTMLEditUtils::IsAnyListElement(atContent.GetContainer())) {
4401 // MOZ_KnownLive because 'arrayOfContents' is guaranteed to
4402 // keep it alive.
4403 nsresult rv =
4404 IndentListChild(&curList, atContent, MOZ_KnownLive(content));
4405 if (NS_FAILED(rv)) {
4406 NS_WARNING("HTMLEditor::IndentListChild() failed");
4407 return rv;
4409 continue;
4412 // Not a list item.
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(
4421 NS_SUCCEEDED(rv),
4422 "HTMLEditor::ChangeMarginStart() failed, but ignored");
4423 curQuote = nullptr;
4424 continue;
4427 if (!curQuote) {
4428 // First, check that our element can contain a div.
4429 if (!HTMLEditUtils::CanNodeContain(*atContent.GetContainer(),
4430 *nsGkAtoms::div)) {
4431 return NS_OK; // cancelled
4434 CreateElementResult createNewDivElementResult =
4435 InsertElementWithSplittingAncestorsWithTransaction(
4436 *nsGkAtoms::div, atContent, BRElementNextToSplitPoint::Keep,
4437 *editingHost);
4438 if (createNewDivElementResult.isErr()) {
4439 NS_WARNING(
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");
4449 return rv;
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(
4458 NS_SUCCEEDED(rv),
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
4468 // keep it alive.
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");
4481 return rv;
4483 NS_WARNING_ASSERTION(
4484 rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
4485 "MoveNodeResult::SuggestCaretPointTo() failed, but ignored");
4487 return NS_OK;
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)) {
4497 NS_WARNING(
4498 "HTMLEditor::MaybeExtendSelectionToHardLineEdgesForBlockEditAction() "
4499 "failed");
4500 return rv;
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(
4512 NS_SUCCEEDED(rv),
4513 "HTMLEditor::HandleHTMLIndentAtSelectionInternal() failed");
4514 return rv;
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)) {
4542 NS_WARNING(
4543 "HTMLEditor::SplitInlinesAndCollectEditTargetNodes(eIndent, "
4544 "CollectNonEditableNodes::Yes) failed");
4545 return rv;
4548 // If there is no visible and editable nodes in the edit targets, make an
4549 // empty block.
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()) {
4568 NS_WARNING(
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
4575 // selection here.
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
4586 // keep it alive.
4587 nsresult rv = DeleteNodeWithTransaction(MOZ_KnownLive(*content));
4588 if (NS_FAILED(rv)) {
4589 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
4590 return rv;
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");
4598 return rv;
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())) {
4608 continue;
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)) {
4614 continue;
4617 if (HTMLEditUtils::IsAnyListElement(atContent.GetContainer())) {
4618 // MOZ_KnownLive because 'arrayOfContents' is guaranteed to
4619 // keep it alive.
4620 nsresult rv =
4621 IndentListChild(&curList, atContent, MOZ_KnownLive(content));
4622 if (NS_FAILED(rv)) {
4623 NS_WARNING("HTMLEditor::IndentListChild() failed");
4624 return rv;
4626 // forget curQuote, if any
4627 curQuote = nullptr;
4628 continue;
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,
4641 editingHost)) {
4642 if (indentedLI == listItem) {
4643 // already indented this list item
4644 continue;
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})
4651 : nullptr;
4652 if (!curList ||
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())
4670 .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");
4678 return rv;
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");
4696 return rv;
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;
4705 continue;
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.
4712 if (curQuote &&
4713 HTMLEditUtils::GetInclusiveAncestorAnyTableElement(*curQuote) !=
4714 HTMLEditUtils::GetInclusiveAncestorAnyTableElement(content)) {
4715 curQuote = nullptr;
4718 if (!curQuote) {
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()) {
4730 NS_WARNING(
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");
4740 return rv;
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
4754 // keep it alive.
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");
4767 return rv;
4769 NS_WARNING_ASSERTION(
4770 rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
4771 "MoveNodeResult::SuggestCaretPointTo() failed, but ignored");
4772 // forget curList, if any
4773 curList = nullptr;
4775 return NS_OK;
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");
4797 return result;
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");
4809 return result;
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(
4819 NS_SUCCEEDED(rv),
4820 "HTMLEditor::MaybeInsertPaddingBRElementForEmptyLastLineAtSelection() "
4821 "failed");
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)) {
4880 NS_WARNING(
4881 "EditorBase::CollapseSelectionTo() caused destroying the editor");
4882 return EditActionHandled(NS_ERROR_EDITOR_DESTROYED);
4884 NS_WARNING_ASSERTION(
4885 NS_SUCCEEDED(rv),
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)) {
4907 NS_WARNING(
4908 "EditorBase::CollapseSelectionTo() caused destroying the editor");
4909 return EditActionHandled(NS_ERROR_EDITOR_DESTROYED);
4911 NS_WARNING_ASSERTION(
4912 NS_SUCCEEDED(rv),
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
4929 // in the range
4930 AutoTArray<OwningNonNull<nsIContent>, 64> arrayOfContents;
4931 nsresult rv = SplitInlinesAndCollectEditTargetNodesInExtendedSelectionRanges(
4932 arrayOfContents, EditSubAction::eOutdent, CollectNonEditableNodes::Yes);
4933 if (NS_FAILED(rv)) {
4934 NS_WARNING(
4935 "HTMLEditor::"
4936 "SplitInlinesAndCollectEditTargetNodesInExtendedSelectionRanges() "
4937 "failed");
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()) {
4951 continue;
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);
4992 continue;
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);
5003 nsAutoString value;
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");
5025 continue;
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
5033 // element, no?
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)) {
5055 NS_WARNING(
5056 "HTMLEditor::LiftUpListItemElement(LiftUpFromAllParentListElements:"
5057 ":No) failed");
5058 return SplitRangeOffFromNodeResult(rv);
5060 continue;
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
5067 // is removed.
5068 if (indentedParentElement) {
5069 if (EditorUtils::IsDescendantOf(*content, *indentedParentElement)) {
5070 // Extend the range to be outdented at removing the
5071 // indentedParentElement.
5072 lastContentToBeOutdented = content;
5073 continue;
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;
5107 break;
5110 if (!useCSS) {
5111 continue;
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);
5123 nsAutoString value;
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?
5134 float startMargin;
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;
5146 break;
5150 if (indentedParentElement) {
5151 continue;
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)) {
5162 continue;
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()) {
5176 continue;
5178 nsresult rv = CollapseSelectionTo(pointToPutCaret);
5179 if (NS_FAILED(rv)) {
5180 NS_WARNING("EditorBase::CollapseSelectionTo() failed");
5181 return SplitRangeOffFromNodeResult(rv);
5183 continue;
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)) {
5199 NS_WARNING(
5200 "HTMLEditor::LiftUpListItemElement("
5201 "LiftUpFromAllParentListElements::No) failed");
5202 return SplitRangeOffFromNodeResult(rv);
5204 continue;
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");
5233 continue;
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()) {
5255 continue;
5257 nsresult rv = CollapseSelectionTo(pointToPutCaret);
5258 if (NS_FAILED(rv)) {
5259 NS_WARNING("EditorBase::CollapseSelectionTo() failed");
5260 return SplitRangeOffFromNodeResult(rv);
5262 continue;
5265 if (useCSS) {
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");
5275 continue;
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()) {
5303 NS_WARNING(
5304 "HTMLEditor::SplitRangeOffFromBlock() caused destorying the editor");
5305 return splitResult;
5307 NS_WARNING_ASSERTION(
5308 splitResult.isOk(),
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;
5371 }();
5372 nsresult rv =
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()) {
5397 NS_WARNING(
5398 "HTMLEditor::SplitRangeOffFromBlock() didn't return middle content");
5399 return SplitRangeOffFromNodeResult(NS_ERROR_FAILURE);
5401 NS_WARNING_ASSERTION(
5402 splitResult.isOk(),
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)) {
5430 NS_WARNING(
5431 "HTMLEditor::ChangeMarginStart(ChangeMargin::Decrease) failed");
5432 return SplitRangeOffFromNodeResult(rv);
5434 return splitResult;
5437 return splitResult;
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()) {
5448 continue;
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();
5471 continue;
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();
5486 continue;
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) {
5535 EditResult result =
5536 ClearStyleAt(pointToPutCaret, MOZ_KnownLive(item->tag),
5537 MOZ_KnownLive(item->attr), item->specifiedStyle);
5538 if (result.Failed()) {
5539 NS_WARNING("HTMLEditor::ClearStyleAt() failed");
5540 return result.Rv();
5542 pointToPutCaret = result.PointRefToCollapseSelection();
5543 item = mTypeInState->TakeClearProperty();
5544 putCaret = true;
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
5554 // nodes above.
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()) {
5561 NS_WARNING(
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");
5571 return rv;
5573 pointToPutCaret = splitTextNodeResult.AtSplitPoint<EditorDOMPoint>();
5575 if (!pointToPutCaret.IsInContentNode() ||
5576 !HTMLEditUtils::IsContainerNode(
5577 *pointToPutCaret.ContainerAsContent())) {
5578 return NS_OK;
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);
5593 putCaret = true;
5595 if (relFontSize) {
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++) {
5600 nsresult rv =
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");
5607 return rv;
5612 while (item) {
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");
5621 return rv;
5623 item = mTypeInState->TakeSetProperty();
5627 if (!putCaret) {
5628 return NS_OK;
5631 nsresult rv = CollapseSelectionTo(pointToPutCaret);
5632 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
5633 "EditorBase::CollapseSelectionTo() failed");
5634 return rv;
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,
5645 ignoredError);
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");
5657 return result;
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(
5692 NS_SUCCEEDED(rv),
5693 "HTMLEditor::PrepareInlineStylesForCaret() failed, but ignored");
5697 if (!SelectionRef().IsCollapsed()) {
5698 nsresult rv = MaybeExtendSelectionToHardLineEdgesForBlockEditAction();
5699 if (NS_FAILED(rv)) {
5700 NS_WARNING(
5701 "HTMLEditor::MaybeExtendSelectionToHardLineEdgesForBlockEditAction() "
5702 "failed");
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
5709 // NS_OK.
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(
5726 NS_SUCCEEDED(rv),
5727 "HTMLEditor::MaybeInsertPaddingBRElementForEmptyLastLineAtSelection() "
5728 "failed");
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
5741 // in the range
5742 AutoTArray<OwningNonNull<nsIContent>, 64> arrayOfContents;
5743 nsresult rv = SplitInlinesAndCollectEditTargetNodesInExtendedSelectionRanges(
5744 arrayOfContents, EditSubAction::eSetOrClearAlignment,
5745 CollectNonEditableNodes::Yes);
5746 if (NS_FAILED(rv)) {
5747 NS_WARNING(
5748 "HTMLEditor::"
5749 "SplitInlinesAndCollectEditTargetNodesInExtendedSelectionRanges("
5750 "eSetOrClearAlignment, CollectNonEditableNodes::Yes) failed");
5751 return rv;
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
5756 // these.
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().
5766 nsresult rv =
5767 SetBlockElementAlign(MOZ_KnownLive(*content->AsElement()), aAlignType,
5768 EditTarget::OnlyDescendantsExceptTable);
5769 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
5770 "HTMLEditor::SetBlockElementAlign() failed");
5771 return rv;
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
5780 // alignment code.
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(
5811 result.Succeeded(),
5812 "HTMLEditor::AlignContentsAtSelectionWithEmptyDivElement() failed");
5813 if (result.Handled()) {
5814 restoreSelectionLater.Abort();
5816 return rv;
5819 rv = AlignNodesAndDescendants(arrayOfContents, aAlignType);
5820 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
5821 "HTMLEditor::AlignNodesAndDescendants() failed");
5822 return rv;
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()) {
5850 NS_WARNING(
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)) {
5867 NS_WARNING(
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
5873 // deleted.
5874 CreateElementResult insertPaddingBRElementResult =
5875 InsertPaddingBRElementForEmptyLastLineWithTransaction(
5876 EditorDOMPoint(newDivElement, 0u));
5877 if (insertPaddingBRElementResult.isErr()) {
5878 NS_WARNING(
5879 "HTMLEditor::InsertPaddingBRElementForEmptyLastLineWithTransaction() "
5880 "failed");
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)) {
5914 continue;
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)) {
5922 nsresult rv =
5923 SetBlockElementAlign(MOZ_KnownLive(*content->AsElement()), aAlignType,
5924 EditTarget::NodeAndDescendantsExceptTable);
5925 if (NS_FAILED(rv)) {
5926 NS_WARNING(
5927 "HTMLEditor::SetBlockElementAlign(EditTarget::"
5928 "NodeAndDescendantsExceptTable) failed");
5929 return rv;
5931 // Clear out createdDivElement so that we don't put nodes after this one
5932 // into it
5933 createdDivElement = nullptr;
5934 continue;
5937 EditorDOMPoint atContent(content);
5938 if (NS_WARN_IF(!atContent.IsSet())) {
5939 continue;
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}))) {
5950 continue;
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)) {
5965 NS_WARNING(
5966 "HTMLEditor::RemoveAlignFromDescendants(EditTarget::"
5967 "OnlyDescendantsExceptTable) failed");
5968 return rv;
5971 if (useCSS) {
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) {
5982 NS_WARNING(
5983 "CSSEditUtils::SetCSSEquivalentToHTMLStyleWithTransaction("
5984 "nsGkAtoms::align) destroyed the editor");
5985 return NS_ERROR_EDITOR_DESTROYED;
5987 NS_WARNING(
5988 "CSSEditUtils::SetCSSEquivalentToHTMLStyleWithTransaction("
5989 "nsGkAtoms::align) failed, but ignored");
5992 createdDivElement = nullptr;
5993 continue;
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)) {
6007 NS_WARNING(
6008 "HTMLEditor::AlignContentsInAllTableCellsAndListItems() failed");
6009 return rv;
6011 createdDivElement = nullptr;
6012 continue;
6015 // Clear out createdDivElement so that we don't put nodes after this one
6016 // into it
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(),
6024 *nsGkAtoms::div)) {
6025 // XXX Why do we return NS_OK here rather than returning error or
6026 // doing continue?
6027 return NS_OK;
6030 CreateElementResult createNewDivElementResult =
6031 InsertElementWithSplittingAncestorsWithTransaction(
6032 *nsGkAtoms::div, atContent, BRElementNextToSplitPoint::Keep,
6033 *editingHost);
6034 if (createNewDivElementResult.isErr()) {
6035 NS_WARNING(
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");
6045 return rv;
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");
6078 return rv;
6080 NS_WARNING_ASSERTION(
6081 rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
6082 "MoveNodeResult::SuggestCaretPointTo() failed, but ignored");
6085 return NS_OK;
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
6106 // keep it alive.
6107 nsresult rv = AlignBlockContentsWithDivElement(
6108 MOZ_KnownLive(tableCellOrListItemElement), aAlignType);
6109 if (NS_FAILED(rv)) {
6110 NS_WARNING("HTMLEditor::AlignBlockContentsWithDivElement() failed");
6111 return rv;
6115 return NS_OK;
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.
6128 return NS_OK;
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,
6139 aAlignType, false);
6140 if (NS_WARN_IF(Destroyed())) {
6141 return NS_ERROR_EDITOR_DESTROYED;
6143 NS_WARNING_ASSERTION(
6144 NS_SUCCEEDED(rv),
6145 "EditorBase::SetAttributeOrEquivalent(nsGkAtoms::align) failed");
6146 return rv;
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(
6163 NS_SUCCEEDED(rv),
6164 nsPrintfCString("EditorBase::SetAttributeOrEquivalent(nsGkAtoms:: "
6165 "align, \"...\", %s) failed",
6166 !aDivElement.IsInComposedDoc() ? "true" : "false")
6167 .get());
6168 return rv;
6170 if (createNewDivElementResult.isErr()) {
6171 NS_WARNING(
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");
6182 return rv;
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)) {
6211 NS_WARNING(
6212 "EditorBase::CollapseSelectionTo() caused destroying the editor");
6213 return NS_ERROR_EDITOR_DESTROYED;
6215 NS_WARNING_ASSERTION(
6216 NS_SUCCEEDED(rv),
6217 "EditorBase::CollapseSelectionTo() failed, but ignored");
6219 return NS_OK;
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) {
6264 return NS_OK;
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()) {
6295 NS_WARNING(
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});
6307 if (child) {
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});
6344 if (child) {
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;
6380 if (*comp == 1) {
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;
6391 if (*comp == 1) {
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.
6397 ErrorResult error;
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?
6422 return point;
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>(
6428 point);
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
6474 // `<blockquote>`.
6475 if (aEditSubAction == EditSubAction::eOutdent &&
6476 point.IsContainerHTMLElement(nsGkAtoms::blockquote)) {
6477 break;
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
6491 // check.
6492 if (!point.GetContainerParent()->IsInclusiveDescendantOf(&aEditingHost) &&
6493 (blockLevelAction ||
6494 !point.GetContainer()->IsInclusiveDescendantOf(&aEditingHost))) {
6495 break;
6498 point.Set(point.GetContainer());
6500 return point;
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?
6517 return point;
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
6531 // of editing host.
6532 if (maybeNonEditableBlockElement == &aEditingHost ||
6533 !maybeNonEditableBlockElement->IsInclusiveDescendantOf(
6534 &aEditingHost)) {
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
6591 // of editing host.
6592 if (maybeNonEditableBlockElement == &aEditingHost ||
6593 !maybeNonEditableBlockElement->IsInclusiveDescendantOf(
6594 &aEditingHost)) {
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())) {
6611 break;
6613 if (HTMLEditUtils::IsVisibleBRElement(*nextEditableContent)) {
6614 break;
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
6621 // node.
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)) {
6636 break;
6639 point.SetAfter(point.GetContainer());
6640 if (NS_WARN_IF(!point.IsSet())) {
6641 break;
6644 return point;
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) {
6713 return;
6716 if (!aStartPoint.IsInContentNode()) {
6717 return;
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) {
6733 return;
6736 // Make sure we don't go higher than our root element in the content tree
6737 if (aEditingHost.IsInclusiveDescendantOf(maybeNonEditableBlockElement)) {
6738 return;
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(),
6752 aRange.EndRef());
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");
6765 return nullptr;
6768 const Element* const editingHost = ComputeEditingHost();
6769 if (NS_WARN_IF(!editingHost)) {
6770 return nullptr;
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");
6780 return nullptr;
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()) {
6793 break;
6795 MOZ_ALWAYS_TRUE(startPoint.RewindOffset());
6798 if (!startPoint.GetChildOrContainerIfDataNode() ||
6799 !startPoint.GetChildOrContainerIfDataNode()->IsInclusiveDescendantOf(
6800 editingHost)) {
6801 return nullptr;
6803 if (endPoint.IsInTextNode()) {
6804 while (!endPoint.IsEndOfContainer()) {
6805 if (!endPoint.IsCharASCIISpaceOrNBSP()) {
6806 break;
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(
6817 editingHost)) {
6818 return nullptr;
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()) {
6832 return nullptr;
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())) {
6846 return nullptr;
6849 const Element* const editingHost = ComputeEditingHost();
6850 if (NS_WARN_IF(!editingHost)) {
6851 return nullptr;
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
6865 // root.
6867 startPoint =
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(
6876 editingHost)) {
6877 return nullptr;
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(
6889 editingHost)) {
6890 return nullptr;
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)) {
6909 return rv;
6911 rv = SplitParentInlineElementsAtRangeEdges(aArrayOfRanges);
6912 NS_WARNING_ASSERTION(
6913 NS_SUCCEEDED(rv),
6914 "HTMLEditor::SplitParentInlineElementsAtRangeEdges() failed");
6915 if (NS_FAILED(rv)) {
6916 return 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)) {
6923 return rv;
6925 rv = MaybeSplitElementsAtEveryBRElement(aOutArrayOfContents, aEditSubAction);
6926 NS_WARNING_ASSERTION(
6927 NS_SUCCEEDED(rv),
6928 "HTMLEditor::MaybeSplitElementsAtEveryBRElement() failed");
6929 return rv;
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()) {
6942 continue;
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();
6951 break;
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(),
6964 ignoredError);
6965 NS_WARNING_ASSERTION(!ignoredError.Failed(),
6966 "nsRange::SetEnd() failed, but ignored");
6967 ignoredError.SuppressException();
6971 if (!pointToPutCaret.IsSet()) {
6972 return rv;
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");
7003 break;
7006 // Then unregister the ranges
7007 for (auto& item : rangeItemArray) {
7008 RangeUpdaterRef().DropRangeItem(item);
7009 RefPtr<nsRange> range = item->GetRange();
7010 if (range) {
7011 aArrayOfRanges.AppendElement(range);
7015 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
7016 return NS_ERROR_EDITOR_DESTROYED;
7018 return NS_OK;
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");
7034 return rv;
7036 if (aOutArrayOfContents.IsEmpty()) {
7037 iter.AppendAllNodesToArray(aOutArrayOfContents);
7038 } else {
7039 AutoTArray<OwningNonNull<nsIContent>, 24> arrayOfTopChildren;
7040 iter.AppendNodesToArray(
7041 +[](nsINode& aNode, void* aArray) -> bool {
7042 MOZ_ASSERT(aArray);
7043 return !static_cast<nsTArray<OwningNonNull<nsIContent>>*>(aArray)
7044 ->Contains(&aNode);
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);
7081 break;
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) {
7102 break;
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) {
7110 break;
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);
7120 break;
7122 aOutArrayOfContents.ReplaceElementAt(
7123 0, OwningNonNull<nsIContent>(*deepestDivBlockquoteOrListElement));
7124 break;
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);
7140 break;
7141 default:
7142 break;
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);
7158 return NS_OK;
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
7180 // alive.
7181 nsresult rv = SplitElementsAtEveryBRElement(MOZ_KnownLive(content),
7182 arrayOfInlineContents);
7183 if (NS_FAILED(rv)) {
7184 NS_WARNING("HTMLEditor::SplitElementsAtEveryBRElement() failed");
7185 return rv;
7188 // Put these nodes in aArrayOfContents, replacing the current node
7189 aArrayOfContents.RemoveElementAt(i);
7190 aArrayOfContents.InsertElementsAt(i, arrayOfInlineContents);
7193 return NS_OK;
7194 default:
7195 return NS_OK;
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);
7207 MOZ_ASSERT(range);
7208 for (nsINode* parent = range->GetClosestCommonInclusiveAncestor(); parent;
7209 parent = parent->GetParentNode()) {
7210 if (HTMLEditUtils::IsAnyListElement(parent)) {
7211 return parent->AsElement();
7215 return nullptr;
7218 nsresult HTMLEditor::SplitParentInlineElementsAtRangeEdges(
7219 RangeItem& aRangeItem) {
7220 MOZ_ASSERT(IsEditActionDataAvailable());
7222 RefPtr<Element> editingHost = ComputeEditingHost();
7223 if (NS_WARN_IF(!editingHost)) {
7224 return NS_OK;
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()) {
7238 NS_WARNING(
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");
7250 return rv;
7252 if (MOZ_UNLIKELY(editingHost != ComputeEditingHost())) {
7253 NS_WARNING(
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())) {
7261 NS_WARNING(
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()) {
7272 return NS_OK;
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()) {
7284 NS_WARNING(
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");
7295 return rv;
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())) {
7303 NS_WARNING(
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();
7312 return NS_OK;
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);
7328 return NS_OK;
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");
7349 return rv;
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");
7377 return rv;
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);
7390 return NS_OK;
7393 // static
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())) {
7417 NS_WARNING(
7418 "WhiteSpaceVisibilityKeeper::PrepareToSplitBlockElement() "
7419 "failed");
7420 return SplitNodeResult(preparationResult.unwrapErr());
7422 EditorDOMPoint pointToSplit = preparationResult.unwrap();
7423 MOZ_ASSERT(pointToSplit.IsInContentNode());
7425 // Split the header
7426 SplitNodeResult splitResult = SplitNodeDeepWithTransaction(
7427 aHeadingElement, pointToSplit,
7428 SplitAtEdges::eAllowToCreateEmptyContainer);
7429 NS_WARNING_ASSERTION(
7430 splitResult.isOk(),
7431 "HTMLEditor::SplitNodeDeepWithTransaction(aHeadingElement, "
7432 "SplitAtEdges::eAllowToCreateEmptyContainer) failed");
7433 return splitResult;
7434 }();
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");
7444 return Err(rv);
7446 if (MOZ_UNLIKELY(!splitHeadingResult.DidSplit())) {
7447 NS_WARNING(
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()) {
7470 NS_WARNING(
7471 "HTMLEditor::InsertPaddingBRElementForEmptyLastLineWithTransaction("
7472 ") failed");
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");
7481 return Err(rv);
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");
7504 return Err(rv);
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
7511 // bug 1405751:
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
7545 ? *nsGkAtoms::p
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(
7564 withTransaction,
7565 EditorDOMPoint(&aDivOrParagraphElement, 0u));
7566 if (insertBRElementResult.isErr()) {
7567 NS_WARNING(
7568 nsPrintfCString("HTMLEditor::InsertBRElement(%s) failed",
7569 ToString(withTransaction).c_str())
7570 .get());
7571 return insertBRElementResult.unwrapErr();
7573 // We'll update selection after inserting the new paragraph.
7574 insertBRElementResult.IgnoreCaretPointSuggestion();
7575 return NS_OK;
7577 if (createNewParagraphElementResult.isErr()) {
7578 NS_WARNING(
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");
7588 return Err(rv);
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
7627 // it.
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?
7636 break;
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?
7668 break;
7671 // XXX Should we check if non-text data node like comment?
7672 if (!nextSibling->IsHTMLElement(nsGkAtoms::br)) {
7673 break;
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})
7698 : nullptr;
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})
7711 : nullptr;
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;
7720 } else {
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
7727 // much simpler.
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())) {
7735 NS_WARNING(
7736 "WhiteSpaceVisibilityKeeper::PrepareToSplitBlockElement() "
7737 "failed");
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
7761 // above.
7762 pointToInsertBR.SetAfter(pointToSplitParentDivOrP.GetContainer());
7764 } else {
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(
7770 atStartOfSelection,
7771 {WalkTreeOption::IgnoreNonEditableNode}, editingHost)
7772 : nullptr;
7773 if (!nearContent || !HTMLEditUtils::IsVisibleBRElement(*nearContent) ||
7774 EditorUtils::IsPaddingBRElementForEmptyLastLine(*nearContent)) {
7775 // is there a BR after it?
7776 nearContent = editingHost ? HTMLEditUtils::GetNextContent(
7777 atStartOfSelection,
7778 {WalkTreeOption::IgnoreNonEditableNode},
7779 editingHost)
7780 : nullptr;
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();
7828 return result;
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())) {
7840 NS_WARNING(
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");
7859 return rv;
7861 if (MOZ_UNLIKELY(!splitDivOrPResult.DidSplit())) {
7862 NS_WARNING(
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");
7884 return rv;
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)) {
7897 NS_WARNING(
7898 "EditorBase::RemoveAttributeWithTransaction(nsGkAtoms::id) failed");
7899 return rv;
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
7907 // also behaves so.
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");
7914 return rv;
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");
7922 return rv;
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)) {
7932 NS_WARNING(
7933 "EditorBase::CollapseSelectionTo() caused destroying the editor");
7934 return NS_ERROR_EDITOR_DESTROYED;
7936 NS_WARNING_ASSERTION(
7937 NS_SUCCEEDED(rv),
7938 "EditorBase::CollapseSelectionToStartOf() failed, but ignored");
7939 } else {
7940 nsresult rv = CollapseSelectionTo(EditorRawDOMPoint(child));
7941 if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) {
7942 NS_WARNING(
7943 "EditorBase::CollapseSelectionTo() caused destroying the editor");
7944 return NS_ERROR_EDITOR_DESTROYED;
7946 NS_WARNING_ASSERTION(
7947 NS_SUCCEEDED(rv),
7948 "EditorBase::CollapseSelectionTo() failed, but ignored");
7950 return NS_OK;
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
7966 // before it.
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())) {
7976 NS_WARNING(
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");
7986 return Err(rv);
7988 leftListElement =
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");
8015 return Err(rv);
8017 nsStaticAtom& newParagraphTagName =
8018 &DefaultParagraphSeparatorTagName() == nsGkAtoms::br
8019 ? *nsGkAtoms::p
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
8032 // connected yet.
8033 const auto withTransaction =
8034 aDivOrParagraphElement.IsInComposedDoc()
8035 ? WithTransaction::Yes
8036 : WithTransaction::No;
8037 CreateElementResult insertBRElementResult =
8038 aHTMLEditor.InsertBRElement(
8039 withTransaction,
8040 EditorDOMPoint(&aDivOrParagraphElement, 0u));
8041 if (insertBRElementResult.isErr()) {
8042 NS_WARNING(
8043 nsPrintfCString("HTMLEditor::InsertBRElement(%s) failed",
8044 ToString(withTransaction).c_str())
8045 .get());
8046 return insertBRElementResult.unwrapErr();
8048 // We'll update selection after inserting the paragraph.
8049 insertBRElementResult.IgnoreCaretPointSuggestion();
8050 return NS_OK;
8052 if (createNewParagraphElementResult.isErr()) {
8053 NS_WARNING(
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");
8063 return Err(rv);
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()) {
8079 NS_WARNING(
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");
8099 return Err(rv);
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
8116 // things.
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
8124 // left empty.
8125 if (HTMLEditUtils::IsEmptyNode(
8126 leftListItemElement,
8127 {EmptyCheckOption::TreatSingleBRElementAsVisible})) {
8128 CreateElementResult insertPaddingBRElementResult =
8129 InsertPaddingBRElementForEmptyLastLineWithTransaction(
8130 EditorDOMPoint(&leftListItemElement, 0u));
8131 if (insertPaddingBRElementResult.isErr()) {
8132 NS_WARNING(
8133 "HTMLEditor::InsertPaddingBRElementForEmptyLastLineWithTransaction("
8134 ") failed");
8135 return Err(insertPaddingBRElementResult.unwrapErr());
8137 // We're returing a candidate point to put caret so that we don't need to
8138 // update now.
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
8150 : *nsGkAtoms::dt;
8151 // MOZ_KnownLive(nextDefinitionListItemTagName) because it's available
8152 // until shutdown.
8153 CreateElementResult createNewListItemElementResult =
8154 CreateAndInsertElement(WithTransaction::Yes,
8155 MOZ_KnownLive(nextDefinitionListItemTagName),
8156 EditorDOMPoint::After(rightListItemElement));
8157 if (createNewListItemElementResult.isErr()) {
8158 NS_WARNING(
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");
8168 return Err(rv);
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");
8181 return Err(rv);
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
8189 // element.
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())) {
8197 NS_WARNING(
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
8253 curBlock = nullptr;
8254 // Recursion time
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");
8260 return rv;
8264 // If the node has different parent than previous node, further nodes in a
8265 // new parent
8266 if (prevParent) {
8267 if (prevParent != content->GetParentNode()) {
8268 // Forget any previous blockquote node we were using
8269 curBlock = nullptr;
8270 prevParent = content->GetParentNode();
8272 } else {
8273 prevParent = content->GetParentNode();
8276 // If no curBlock, make one
8277 if (!curBlock) {
8278 CreateElementResult createNewBlockQuoteElementResult =
8279 InsertElementWithSplittingAncestorsWithTransaction(
8280 *nsGkAtoms::blockquote, EditorDOMPoint(content),
8281 BRElementNextToSplitPoint::Keep, *editingHost);
8282 if (createNewBlockQuoteElementResult.isErr()) {
8283 NS_WARNING(
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");
8293 return rv;
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");
8317 return rv;
8319 NS_WARNING_ASSERTION(
8320 rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
8321 "MoveNodeResult::SuggestCaretPointTo() failed, but ignored");
8323 return NS_OK;
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
8332 // inline things...
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
8339 if (blockElement) {
8340 SplitRangeOffFromNodeResult removeMiddleContainerResult =
8341 SplitRangeOffFromBlockAndRemoveMiddleContainer(
8342 *blockElement, *firstContent, *lastContent);
8343 if (removeMiddleContainerResult.isErr()) {
8344 NS_WARNING(
8345 "HTMLEditor::SplitRangeOffFromBlockAndRemoveMiddleContainer() "
8346 "failed");
8347 return removeMiddleContainerResult.unwrapErr();
8349 firstContent = lastContent = blockElement = nullptr;
8351 if (!EditorUtils::IsEditableContent(content, EditorType::HTML)) {
8352 continue;
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()) {
8364 continue;
8366 nsresult rv = CollapseSelectionTo(pointToPutCaret);
8367 if (NS_FAILED(rv)) {
8368 NS_WARNING("EditorBase::CollapseSelectionTo() failed");
8369 return rv;
8371 continue;
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
8380 if (blockElement) {
8381 SplitRangeOffFromNodeResult removeMiddleContainerResult =
8382 SplitRangeOffFromBlockAndRemoveMiddleContainer(
8383 *blockElement, *firstContent, *lastContent);
8384 if (removeMiddleContainerResult.isErr()) {
8385 NS_WARNING(
8386 "HTMLEditor::SplitRangeOffFromBlockAndRemoveMiddleContainer() "
8387 "failed");
8388 return removeMiddleContainerResult.unwrapErr();
8390 firstContent = lastContent = blockElement = nullptr;
8392 if (!EditorUtils::IsEditableContent(content, EditorType::HTML)) {
8393 continue;
8395 // Recursion time
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");
8401 return rv;
8403 continue;
8406 if (HTMLEditUtils::IsInlineElement(content)) {
8407 if (blockElement) {
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;
8412 continue;
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()) {
8421 NS_WARNING(
8422 "HTMLEditor::SplitRangeOffFromBlockAndRemoveMiddleContainer() "
8423 "failed");
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;
8435 } else {
8436 firstContent = lastContent = content;
8438 continue;
8441 if (blockElement) {
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()) {
8448 NS_WARNING(
8449 "HTMLEditor::SplitRangeOffFromBlockAndRemoveMiddleContainer() "
8450 "failed");
8451 return removeMiddleContainerResult.unwrapErr();
8453 firstContent = lastContent = blockElement = nullptr;
8454 continue;
8457 // Process any partial progress saved
8458 if (blockElement) {
8459 SplitRangeOffFromNodeResult removeMiddleContainerResult =
8460 SplitRangeOffFromBlockAndRemoveMiddleContainer(
8461 *blockElement, *firstContent, *lastContent);
8462 if (removeMiddleContainerResult.isErr()) {
8463 NS_WARNING(
8464 "HTMLEditor::SplitRangeOffFromBlockAndRemoveMiddleContainer() "
8465 "failed");
8466 return removeMiddleContainerResult.unwrapErr();
8468 firstContent = lastContent = blockElement = nullptr;
8470 return NS_OK;
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
8484 // inline things...
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
8492 // block.
8493 curBlock = nullptr;
8494 newBlock = nullptr;
8495 continue;
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
8503 curBlock = nullptr;
8504 // Do nothing to this block
8505 continue;
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
8514 curBlock = nullptr;
8515 CreateElementResult newBlockElementOrError =
8516 ReplaceContainerAndCloneAttributesWithTransaction(
8517 MOZ_KnownLive(*content->AsElement()), aBlockTag);
8518 if (newBlockElementOrError.isErr()) {
8519 NS_WARNING(
8520 "EditorBase::ReplaceContainerAndCloneAttributesWithTransaction() "
8521 "failed");
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");
8537 return rv;
8539 NS_WARNING_ASSERTION(
8540 rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
8541 "CreateElementResult::SuggestCaretPointTo() failed, but ignored");
8542 newBlock = newBlockElementOrError.UnwrapNewNode();
8543 continue;
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
8552 curBlock = nullptr;
8553 // Recursion time
8554 AutoTArray<OwningNonNull<nsIContent>, 24> childContents;
8555 HTMLEditor::GetChildNodesOf(*content, childContents);
8556 if (!childContents.IsEmpty()) {
8557 nsresult rv =
8558 CreateOrChangeBlockContainerElement(childContents, aBlockTag);
8559 if (NS_FAILED(rv)) {
8560 NS_WARNING(
8561 "HTMLEditor::CreateOrChangeBlockContainerElement() failed");
8562 return rv;
8564 continue;
8567 // Make sure we can put a block here
8568 CreateElementResult createNewBlockElementResult =
8569 InsertElementWithSplittingAncestorsWithTransaction(
8570 aBlockTag, atContent, BRElementNextToSplitPoint::Keep,
8571 *editingHost);
8572 if (createNewBlockElementResult.isErr()) {
8573 NS_WARNING(
8574 nsPrintfCString(
8575 "HTMLEditor::"
8576 "InsertElementWithSplittingAncestorsWithTransaction(%s) failed",
8577 nsAtomCString(&aBlockTag).get())
8578 .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");
8586 return rv;
8588 MOZ_ASSERT(createNewBlockElementResult.GetNewNode());
8589 // Remember our new block for postprocessing
8590 TopLevelEditSubActionDataRef().mNewBlockElement =
8591 createNewBlockElementResult.UnwrapNewNode();
8592 continue;
8595 if (content->IsHTMLElement(nsGkAtoms::br)) {
8596 // If the node is a break, we honor it by putting further nodes in a new
8597 // parent
8598 if (curBlock) {
8599 // Forget any previous block used for previous inline nodes
8600 curBlock = nullptr;
8601 // MOZ_KnownLive because 'aArrayOfContents' is guaranteed to keep it
8602 // alive.
8603 nsresult rv = DeleteNodeWithTransaction(MOZ_KnownLive(*content));
8604 if (NS_FAILED(rv)) {
8605 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
8606 return rv;
8608 continue;
8611 // The break is the first (or even only) node we encountered. Create a
8612 // block for it.
8613 CreateElementResult createNewBlockElementResult =
8614 InsertElementWithSplittingAncestorsWithTransaction(
8615 aBlockTag, atContent, BRElementNextToSplitPoint::Keep,
8616 *editingHost);
8617 if (createNewBlockElementResult.isErr()) {
8618 NS_WARNING(
8619 nsPrintfCString(
8620 "HTMLEditor::"
8621 "InsertElementWithSplittingAncestorsWithTransaction(%s) failed",
8622 nsAtomCString(&aBlockTag).get())
8623 .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");
8631 return rv;
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
8640 // alive.
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");
8653 return rv;
8655 NS_WARNING_ASSERTION(
8656 rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
8657 "MoveNodeResult::SuggestCaretPointTo() failed, but ignored");
8658 curBlock = std::move(newBlockElement);
8659 continue;
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
8673 continue;
8676 // If no curBlock, make one
8677 if (!curBlock) {
8678 CreateElementResult createNewBlockElementResult =
8679 InsertElementWithSplittingAncestorsWithTransaction(
8680 aBlockTag, atContent, BRElementNextToSplitPoint::Keep,
8681 *editingHost);
8682 if (createNewBlockElementResult.isErr()) {
8683 NS_WARNING(nsPrintfCString("HTMLEditor::"
8684 "InsertElementWithSplittingAncestorsWithTr"
8685 "ansaction(%s) failed",
8686 nsAtomCString(&aBlockTag).get())
8687 .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");
8695 return rv;
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");
8734 return rv;
8736 NS_WARNING_ASSERTION(
8737 rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
8738 "MoveNodeResult::SuggestCaretPointTo() failed, but ignored");
8741 return NS_OK;
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(),
8761 *host)) {
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) {
8773 NS_WARNING(
8774 "HTMLEditor::MaybeSplitAncestorsForInsertWithTransaction() reached "
8775 "editing host");
8776 return SplitNodeResult(NS_ERROR_FAILURE);
8779 if (HTMLEditUtils::CanNodeContain(*pointToInsert.GetContainer(), aTag)) {
8780 // Found an ancestor node which can contain the element.
8781 break;
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
8789 // as is.
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;
8804 CreateElementResult
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()) {
8815 NS_WARNING(
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(
8839 splitPoint,
8840 {WalkTreeOption::IgnoreNonEditableNode,
8841 WalkTreeOption::StopAtBlockBoundary},
8842 &aEditingHost)) {
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()) {
8867 NS_WARNING(
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
8897 // the left one
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");
8911 return rv;
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(
8964 NS_SUCCEEDED(rv),
8965 "HTMLEditor::JoinNearestEditableNodesWithTransaction() failed");
8966 return rv;
8968 *aNewFirstChildOfRightNode = joinNodesResult.AtJoinedPoint<EditorDOMPoint>();
8969 return NS_OK;
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;
8980 continue;
8982 if (element->IsHTMLElement(nsGkAtoms::body)) {
8983 break;
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");
8996 return rv;
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,
9007 nsGkAtoms::i,
9008 nsGkAtoms::u,
9009 nsGkAtoms::s,
9010 nsGkAtoms::strike,
9011 nsGkAtoms::face,
9012 nsGkAtoms::size,
9013 nsGkAtoms::color,
9014 nsGkAtoms::tt,
9015 nsGkAtoms::em,
9016 nsGkAtoms::strong,
9017 nsGkAtoms::dfn,
9018 nsGkAtoms::code,
9019 nsGkAtoms::samp,
9020 nsGkAtoms::var,
9021 nsGkAtoms::cite,
9022 nsGkAtoms::abbr,
9023 nsGkAtoms::acronym,
9024 nsGkAtoms::backgroundColor,
9025 nsGkAtoms::sub,
9026 nsGkAtoms::sup}) {
9027 nsStaticAtom *tag, *attribute;
9028 if (property == nsGkAtoms::face || property == nsGkAtoms::size ||
9029 property == nsGkAtoms::color) {
9030 tag = nsGkAtoms::font;
9031 attribute = property;
9032 } else {
9033 tag = 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);
9039 if (typeInSet) {
9040 continue;
9042 bool isSet = false;
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);
9049 } else {
9050 Result<bool, nsresult> isComputedCSSEquivalentToHTMLInlineStyleOrError =
9051 mCSSEditUtils->IsComputedCSSEquivalentToHTMLInlineStyleSet(
9052 aContent, MOZ_KnownLive(tag), MOZ_KnownLive(attribute), value);
9053 if (isComputedCSSEquivalentToHTMLInlineStyleOrError.isErr()) {
9054 NS_WARNING(
9055 "CSSEditUtils::IsComputedCSSEquivalentToHTMLInlineStyleSet failed");
9056 return isComputedCSSEquivalentToHTMLInlineStyleOrError.unwrapErr();
9058 isSet = isComputedCSSEquivalentToHTMLInlineStyleOrError.unwrap();
9060 if (isSet) {
9061 aStyleCacheArray.AppendElement(StyleCache(tag, attribute, value));
9064 return NS_OK;
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()) {
9076 return NS_OK;
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()
9088 : nullptr;
9089 if (NS_WARN_IF(!startContainerContent)) {
9090 return NS_OK;
9093 AutoStyleCacheArray styleCacheArrayAtInsertionPoint;
9094 nsresult rv =
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");
9101 return NS_OK;
9104 for (StyleCache& styleCacheBeforeEdit :
9105 *TopLevelEditSubActionDataRef().mCachedInlineStyles) {
9106 bool isFirst = false, isAny = false, isAll = false;
9107 nsAutoString currentValue;
9108 if (useCSS) {
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()) {
9115 NS_WARNING(
9116 "CSSEditUtils::IsComputedCSSEquivalentToHTMLInlineStyleSet() "
9117 "failed");
9118 return isComputedCSSEquivalentToHTMLInlineStyleOrError.unwrapErr();
9120 isAny = isComputedCSSEquivalentToHTMLInlineStyleOrError.unwrap();
9122 if (!isAny) {
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,
9128 &currentValue);
9129 if (NS_FAILED(rv)) {
9130 NS_WARNING("HTMLEditor::GetInlinePropertyBase() failed");
9131 return rv;
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())) {
9137 continue;
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());
9150 return NS_OK;
9153 nsresult HTMLEditor::InsertBRElementToEmptyListItemsAndTableCellsInRange(
9154 const RawRangeBoundary& aStartRef, const RawRangeBoundary& aEndRef) {
9155 MOZ_ASSERT(IsEditActionDataAvailable());
9157 AutoTArray<OwningNonNull<Element>, 64> arrayOfEmptyElements;
9158 DOMIterator iter;
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));
9166 MOZ_ASSERT(aSelf);
9167 Element* element = aNode.AsElement();
9168 if (!EditorUtils::IsEditableContent(*element, EditorType::HTML) ||
9169 (!HTMLEditUtils::IsListItem(element) &&
9170 !HTMLEditUtils::IsTableCellOrCaption(*element))) {
9171 return false;
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
9184 // is in this node.
9185 EditorDOMPoint endOfNode(EditorDOMPoint::AtEndOf(emptyElement));
9186 CreateElementResult insertPaddingBRElementResult =
9187 InsertPaddingBRElementForEmptyLastLineWithTransaction(endOfNode);
9188 if (insertPaddingBRElementResult.isErr()) {
9189 NS_WARNING(
9190 "HTMLEditor::InsertPaddingBRElementForEmptyLastLineWithTransaction() "
9191 "failed");
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)) {
9202 NS_WARNING(
9203 "EditorBase::CollapseSelectionTo() caused destroying the editor");
9204 return NS_ERROR_EDITOR_DESTROYED;
9206 NS_WARNING_ASSERTION(
9207 NS_SUCCEEDED(rv),
9208 "EditorBase::CollapseSelectionTo() failed, but ignored");
9210 return NS_OK;
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());
9227 if (!staticRange) {
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");
9237 return rv;
9240 if (nodeBefore && nodeAfter) {
9241 return NS_OK; // selection is inside block
9244 if (nodeBefore) {
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);
9255 } else {
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");
9264 return rv;
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);
9277 } else {
9278 atStartOfBlock.Set(firstEditableContent, 0);
9280 rv = CollapseSelectionTo(atStartOfBlock);
9281 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
9282 "EditorBase::CollapseSelectionTo() failed");
9283 return rv;
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)) {
9293 return;
9296 EditorDOMPoint atCaret(firstRange->StartRef());
9297 if (NS_WARN_IF(!atCaret.IsSet())) {
9298 return;
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(
9311 atCaret,
9312 {WalkTreeOption::IgnoreNonEditableNode,
9313 WalkTreeOption::StopAtBlockBoundary},
9314 editingHost)) {
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");
9322 return;
9327 if (!atCaret.GetChild()) {
9328 return;
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");
9344 return;
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?
9409 return NS_OK;
9411 CreateElementResult insertPaddingBRElementResult =
9412 InsertPaddingBRElementForEmptyLastLineWithTransaction(point);
9413 if (insertPaddingBRElementResult.isErr()) {
9414 NS_WARNING(
9415 "HTMLEditor::InsertPaddingBRElementForEmptyLastLineWithTransaction("
9416 ") failed");
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");
9425 return rv;
9427 NS_WARNING_ASSERTION(
9428 rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
9429 "CreateElementResult::SuggestCaretPointTo() failed, but ignored");
9430 return NS_OK;
9434 // XXX Perhaps, we should do something if we're in a data node but not
9435 // a text node.
9436 if (point.IsInTextNode()) {
9437 return NS_OK;
9440 // Do we need to insert a padding <br> element for empty last line? We do
9441 // if we are:
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();
9446 if (!editingHost) {
9447 return NS_OK;
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()) {
9477 NS_WARNING(
9478 "HTMLEditor::"
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");
9488 return rv;
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},
9498 editingHost)) {
9499 if (EditorUtils::IsPaddingBRElementForEmptyLastLine(
9500 *nextEditableContentInBlock)) {
9501 // Make it stick to the padding `<br>` element so that it will be
9502 // on blank line.
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(
9518 point,
9519 {WalkTreeOption::IgnoreNonEditableNode,
9520 WalkTreeOption::StopAtBlockBoundary},
9521 editingHost)) {
9522 if (previousEditableContentInBlock->IsHTMLElement(nsGkAtoms::br) ||
9523 previousEditableContentInBlock->IsText() ||
9524 HTMLEditUtils::IsImage(previousEditableContentInBlock) ||
9525 previousEditableContentInBlock->IsHTMLElement(nsGkAtoms::hr)) {
9526 return NS_OK;
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},
9536 editingHost)) {
9537 if (nextEditableContentInBlock->IsText() ||
9538 nextEditableContentInBlock->IsAnyOfHTMLElements(
9539 nsGkAtoms::br, nsGkAtoms::img, nsGkAtoms::hr)) {
9540 return NS_OK;
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(
9549 point,
9550 aDirectionAndAmount == nsIEditor::ePrevious ? WalkTreeDirection::Backward
9551 : WalkTreeDirection::Forward,
9552 *editingHost);
9553 if (!nearEditableContent) {
9554 return NS_OK;
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");
9567 return rv;
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
9589 // node's parent.
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
9599 // parent.
9601 PostContentIterator postOrderIter;
9602 nsresult rv = postOrderIter.Init(&aRange);
9603 if (NS_FAILED(rv)) {
9604 NS_WARNING("PostContentIterator::Init() failed");
9605 return rv;
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;
9625 continue;
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
9640 isCandidate = true;
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());
9648 isCandidate =
9649 !selectionRanges
9650 .IsAtLeastOneContainerOfRangeBoundariesInclusiveDescendantOf(
9651 *content);
9655 bool isEmptyNode = false;
9656 if (isCandidate) {
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};
9662 if (!isMailCite) {
9663 options += EmptyCheckOption::TreatSingleBRElementAsVisible;
9665 isEmptyNode = HTMLEditUtils::IsEmptyNode(*content, options);
9666 if (isEmptyNode) {
9667 if (isMailCite) {
9668 // mailcites go on a separate list from other empty nodes
9669 arrayOfEmptyCites.AppendElement(*content);
9670 } else {
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
9687 // alive.
9688 nsresult rv = DeleteNodeWithTransaction(MOZ_KnownLive(emptyContent));
9689 if (NS_FAILED(rv)) {
9690 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
9691 return rv;
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");
9723 return rv;
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)) {
9730 NS_WARNING(
9731 "EditorBase::CollapseSelectionTo() caused destroying the editor");
9732 return NS_ERROR_EDITOR_DESTROYED;
9734 NS_WARNING_ASSERTION(
9735 NS_SUCCEEDED(rv),
9736 "EditorBase::CollapseSelectionTo() failed, but ignored");
9739 return NS_OK;
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
9757 // otherwise we do.
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");
9786 return rv;
9789 leftListElement =
9790 Element::FromNodeOrNull(splitListItemParentResult.GetPreviousContent());
9791 if (MOZ_UNLIKELY(!leftListElement)) {
9792 NS_WARNING(
9793 "HTMLEditor::SplitNodeWithTransaction() didn't return left list "
9794 "element");
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()) {
9847 return NS_OK;
9849 nsresult rv = CollapseSelectionTo(pointToPutCaret);
9850 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
9851 "EditorBase::CollapseSelectionTo() failed");
9852 return rv;
9855 if (pointToPutCaret.IsSet()) {
9856 nsresult rv = CollapseSelectionTo(pointToPutCaret);
9857 if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) {
9858 NS_WARNING("EditorBase::CollapseSelectionTo() failed");
9859 return rv;
9861 NS_WARNING_ASSERTION(
9862 NS_SUCCEEDED(rv),
9863 "EditorBase::CollapseSelectionTo() failed, but ignored");
9866 if (aLiftUpFromAllParentListElements == LiftUpFromAllParentListElements::No) {
9867 return NS_OK;
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");
9876 return rv;
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)) {
9902 NS_WARNING(
9903 "HTMLEditor::LiftUpListItemElement(LiftUpFromAllParentListElements:"
9904 ":Yes) failed");
9905 return rv;
9907 continue;
9910 if (HTMLEditUtils::IsAnyListElement(child)) {
9911 nsresult rv =
9912 DestroyListStructureRecursively(MOZ_KnownLive(*child->AsElement()));
9913 if (NS_FAILED(rv)) {
9914 NS_WARNING("HTMLEditor::DestroyListStructureRecursively() failed");
9915 return rv;
9917 continue;
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");
9927 return rv;
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()) {
9940 return NS_OK;
9942 nsresult rv = CollapseSelectionTo(pointToPutCaret);
9943 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
9944 "EditorBase::CollapseSelectionTo() failed");
9945 return rv;
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.
9975 if (!temp) {
9976 nsresult rv = CollapseSelectionToStartOf(*bodyOrDocumentElement);
9977 if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) {
9978 NS_WARNING(
9979 "EditorBase::CollapseSelectionToStartOf() caused destroying the "
9980 "editor");
9981 return NS_ERROR_EDITOR_DESTROYED;
9983 NS_WARNING_ASSERTION(
9984 NS_SUCCEEDED(rv),
9985 "EditorBase::CollapseSelectionToStartOf() failed, but ignored");
9986 return NS_OK;
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.
10002 if (!temp) {
10003 nsresult rv = CollapseSelectionToStartOf(*bodyOrDocumentElement);
10004 if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) {
10005 NS_WARNING(
10006 "EditorBase::CollapseSelectionToStartOf() caused destroying the "
10007 "editor");
10008 return NS_ERROR_EDITOR_DESTROYED;
10010 NS_WARNING_ASSERTION(
10011 NS_SUCCEEDED(rv),
10012 "EditorBase::CollapseSelectionToStartOf() failed, but ignored");
10015 return NS_OK;
10018 nsresult HTMLEditor::InsertPaddingBRElementForEmptyLastLineIfNeeded(
10019 Element& aElement) {
10020 MOZ_ASSERT(IsEditActionDataAvailable());
10022 if (!HTMLEditUtils::IsBlockElement(aElement)) {
10023 return NS_OK;
10026 if (!HTMLEditUtils::IsEmptyNode(
10027 aElement, {EmptyCheckOption::TreatSingleBRElementAsVisible})) {
10028 return NS_OK;
10031 CreateElementResult insertPaddingBRElementResult =
10032 InsertPaddingBRElementForEmptyLastLineWithTransaction(
10033 EditorDOMPoint(&aElement, 0u));
10034 if (insertPaddingBRElementResult.isErr()) {
10035 NS_WARNING(
10036 "HTMLEditor::InsertPaddingBRElementForEmptyLastLineWithTransaction() "
10037 "failed");
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");
10046 return rv;
10048 NS_WARNING_ASSERTION(
10049 rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
10050 "CreateElementResult::SuggestCaretPointTo() failed, but ignored");
10051 return NS_OK;
10054 nsresult HTMLEditor::InsertBRElementIfEmptyBlockElement(Element& aElement) {
10055 MOZ_ASSERT(IsEditActionDataAvailable());
10057 if (!HTMLEditUtils::IsBlockElement(aElement)) {
10058 return NS_OK;
10061 if (!HTMLEditUtils::IsEmptyNode(
10062 aElement, {EmptyCheckOption::TreatSingleBRElementAsVisible})) {
10063 return NS_OK;
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");
10079 return rv;
10081 NS_WARNING_ASSERTION(
10082 rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
10083 "CreateElementResult::SuggestCaretPointTo() failed, but ignored");
10084 MOZ_ASSERT(insertBRElementResult.GetNewNode());
10085 return NS_OK;
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
10103 ? &aElement
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
10112 ? nullptr
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)) {
10120 NS_WARNING(
10121 "HTMLEditor::RemoveAlignFromDescendants(EditTarget::"
10122 "OnlyDescendantsExceptTable) failed");
10123 return rv;
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");
10132 return rv;
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");
10141 return rv;
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()) {
10153 continue;
10155 if (NS_FAILED(rv = CollapseSelectionTo(pointToPutCaret))) {
10156 NS_WARNING("EditorBase::CollapseSelectionTo() failed");
10157 return rv;
10159 continue;
10162 if (!HTMLEditUtils::IsBlockElement(*content) &&
10163 !content->IsHTMLElement(nsGkAtoms::hr)) {
10164 continue;
10167 OwningNonNull<Element> blockOrHRElement = *content->AsElement();
10168 if (HTMLEditUtils::SupportsAlignAttr(blockOrHRElement)) {
10169 nsresult rv =
10170 RemoveAttributeWithTransaction(blockOrHRElement, *nsGkAtoms::align);
10171 if (NS_WARN_IF(Destroyed())) {
10172 return NS_ERROR_EDITOR_DESTROYED;
10174 if (NS_FAILED(rv)) {
10175 NS_WARNING(
10176 "EditorBase::RemoveAttributeWithTransaction(nsGkAtoms::align) "
10177 "failed");
10178 return rv;
10181 if (useCSS) {
10182 if (blockOrHRElement->IsAnyOfHTMLElements(nsGkAtoms::table,
10183 nsGkAtoms::hr)) {
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)) {
10190 NS_WARNING(
10191 "EditorBase::SetAttributeOrEquivalent(nsGkAtoms::align) failed");
10192 return rv;
10194 } else {
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,
10205 dummyCssValue);
10206 if (NS_FAILED(rv)) {
10207 NS_WARNING(
10208 "CSSEditUtils::RemoveCSSInlineStyleWithTransaction(nsGkAtoms::"
10209 "textAlign) failed");
10210 return rv;
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)) {
10219 NS_WARNING(
10220 "HTMLEditor::RemoveAlignFromDescendants(EditTarget::"
10221 "OnlyDescendantsExceptTable) failed");
10222 return rv;
10226 return NS_OK;
10229 nsresult HTMLEditor::EnsureHardLineBeginsWithFirstChildOf(
10230 Element& aRemovingContainerElement) {
10231 MOZ_ASSERT(IsEditActionDataAvailable());
10233 nsIContent* firstEditableChild = HTMLEditUtils::GetFirstChild(
10234 aRemovingContainerElement, {WalkTreeOption::IgnoreNonEditableNode});
10235 if (!firstEditableChild) {
10236 return NS_OK;
10239 if (HTMLEditUtils::IsBlockElement(*firstEditableChild) ||
10240 firstEditableChild->IsHTMLElement(nsGkAtoms::br)) {
10241 return NS_OK;
10244 nsIContent* previousEditableContent = HTMLEditUtils::GetPreviousSibling(
10245 aRemovingContainerElement, {WalkTreeOption::IgnoreNonEditableNode});
10246 if (!previousEditableContent) {
10247 return NS_OK;
10250 if (HTMLEditUtils::IsBlockElement(*previousEditableContent) ||
10251 previousEditableContent->IsHTMLElement(nsGkAtoms::br)) {
10252 return NS_OK;
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");
10268 return rv;
10270 NS_WARNING_ASSERTION(
10271 rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
10272 "CreateElementResult::SuggestCaretPointTo() failed, but ignored");
10273 MOZ_ASSERT(insertBRElementResult.GetNewNode());
10274 return NS_OK;
10277 nsresult HTMLEditor::EnsureHardLineEndsWithLastChildOf(
10278 Element& aRemovingContainerElement) {
10279 MOZ_ASSERT(IsEditActionDataAvailable());
10281 nsIContent* firstEditableContent = HTMLEditUtils::GetLastChild(
10282 aRemovingContainerElement, {WalkTreeOption::IgnoreNonEditableNode});
10283 if (!firstEditableContent) {
10284 return NS_OK;
10287 if (HTMLEditUtils::IsBlockElement(*firstEditableContent) ||
10288 firstEditableContent->IsHTMLElement(nsGkAtoms::br)) {
10289 return NS_OK;
10292 nsIContent* nextEditableContent = HTMLEditUtils::GetPreviousSibling(
10293 aRemovingContainerElement, {WalkTreeOption::IgnoreNonEditableNode});
10294 if (!nextEditableContent) {
10295 return NS_OK;
10298 if (HTMLEditUtils::IsBlockElement(*nextEditableContent) ||
10299 nextEditableContent->IsHTMLElement(nsGkAtoms::br)) {
10300 return NS_OK;
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");
10315 return rv;
10317 NS_WARNING_ASSERTION(
10318 rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
10319 "CreateElementResult::SuggestCaretPointTo() failed, but ignored");
10320 MOZ_ASSERT(insertBRElementResult.GetNewNode());
10321 return NS_OK;
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)) {
10334 nsresult rv =
10335 RemoveAlignFromDescendants(aBlockOrHRElement, aAlignType, aEditTarget);
10336 if (NS_FAILED(rv)) {
10337 NS_WARNING("HTMLEditor::RemoveAlignFromDescendants() failed");
10338 return rv;
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(
10347 NS_SUCCEEDED(rv),
10348 "HTMLEditor::SetAttributeOrEquivalent(nsGkAtoms::align) failed");
10349 return rv;
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");
10369 float f;
10370 RefPtr<nsAtom> unit;
10371 CSSEditUtils::ParseLength(value, &f, getter_AddRefs(unit));
10372 if (!f) {
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;
10398 if (0 < f) {
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),
10408 newValue);
10409 if (rv == NS_ERROR_EDITOR_DESTROYED) {
10410 NS_WARNING(
10411 "CSSEditUtils::SetCSSPropertyWithTransaction() destroyed the "
10412 "editor");
10413 return NS_ERROR_EDITOR_DESTROYED;
10415 NS_WARNING_ASSERTION(
10416 NS_SUCCEEDED(rv),
10417 "CSSEditUtils::SetCSSPropertyWithTransaction() failed, but ignored");
10419 return NS_OK;
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) {
10429 NS_WARNING(
10430 "CSSEditUtils::RemoveCSSPropertyWithTransaction() destroyed the "
10431 "editor");
10432 return NS_ERROR_EDITOR_DESTROYED;
10434 NS_WARNING_ASSERTION(
10435 NS_SUCCEEDED(rv),
10436 "CSSEditUtils::RemoveCSSPropertyWithTransaction() failed, but ignored");
10439 // Remove unnecessary divs
10440 if (!aElement.IsHTMLElement(nsGkAtoms::div) ||
10441 HTMLEditor::HasAttributes(&aElement)) {
10442 return NS_OK;
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)) {
10448 return NS_OK;
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()) {
10459 return NS_OK;
10461 nsresult rv = CollapseSelectionTo(pointToPutCaret);
10462 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
10463 "EditorBase::CollapseSelectionTo() failed");
10464 return rv;
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,
10475 ignoredError);
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");
10487 return result;
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(
10512 NS_SUCCEEDED(rv),
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)) {
10526 NS_WARNING(
10527 "HTMLEditor::MaybeExtendSelectionToHardLineEdgesForBlockEditAction() "
10528 "failed");
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)) {
10546 NS_WARNING(
10547 "HTMLEditor::MoveSelectedContentsToDivElementToMakeItAbsolutePosition()"
10548 " failed");
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);
10562 if (!divElement) {
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)) {
10599 NS_WARNING(
10600 "HTMLEditor::SplitInlinesAndCollectEditTargetNodes("
10601 "eSetPositionToAbsolute, CollectNonEditableNodes::Yes) failed");
10602 return rv;
10605 // If there is no visible and editable nodes in the edit targets, make an
10606 // empty block.
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,
10623 *editingHost);
10624 if (createNewDivElementResult.isErr()) {
10625 NS_WARNING(
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
10632 // selection here.
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");
10643 return rv;
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);
10652 return rv;
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)) {
10673 continue;
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>`
10683 // element.
10684 nsIContent* previousEditableContent =
10685 createdListElement
10686 ? HTMLEditUtils::GetPreviousSibling(
10687 content, {WalkTreeOption::IgnoreNonEditableNode})
10688 : nullptr;
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()) {
10701 NS_WARNING(
10702 "HTMLEditor::MaybeSplitAncestorsForInsertWithTransaction() "
10703 "failed");
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();
10709 } else {
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,
10715 *editingHost);
10716 if (createNewDivElementResult.isErr()) {
10717 NS_WARNING(
10718 "HTMLEditor::"
10719 "InsertElementWithSplittingAncestorsWithTransaction(nsGkAtoms::"
10720 "div) failed");
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()) {
10733 NS_WARNING(
10734 "HTMLEditor::CreateAndInsertElement(WithTransaction::Yes) "
10735 "failed");
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");
10744 return Err(rv);
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
10754 // absolutely.
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");
10768 return Err(rv);
10770 NS_WARNING_ASSERTION(
10771 rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
10772 "MoveNodeResult::SuggestCaretPointTo() failed, but ignored");
10773 continue;
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,
10781 editingHost)) {
10782 if (handledListItemElement == listItemElement) {
10783 // Current node has already been moved into the `<div>` element.
10784 continue;
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 =
10789 createdListElement
10790 ? HTMLEditUtils::GetPreviousSibling(
10791 *listItemElement, {WalkTreeOption::IgnoreNonEditableNode})
10792 : nullptr;
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()) {
10811 NS_WARNING(
10812 "HTMLEditor::MaybeSplitAncestorsForInsertWithTransaction() "
10813 "failed");
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();
10819 } else {
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,
10825 *editingHost);
10826 if (createNewDivElementResult.isErr()) {
10827 NS_WARNING(
10828 "HTMLEditor::"
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()) {
10844 NS_WARNING(
10845 "HTMLEditor::CreateAndInsertElement(WithTransaction::Yes) "
10846 "failed");
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");
10855 return Err(rv);
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");
10878 return Err(rv);
10880 NS_WARNING_ASSERTION(
10881 rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
10882 "MoveNodeResult::SuggestCaretPointTo() failed, but ignored");
10883 handledListItemElement = std::move(listItemElement);
10884 continue;
10887 if (!targetDivElement) {
10888 // If we meet a `<div>` element, use it as the absolute-position
10889 // container.
10890 // XXX This looks odd. If there are 2 or more `<div>` elements are
10891 // selected, first found `<div>` element will have all other
10892 // selected nodes.
10893 if (content->IsHTMLElement(nsGkAtoms::div)) {
10894 targetDivElement = content->AsElement();
10895 MOZ_ASSERT(!createdListElement);
10896 MOZ_ASSERT(!handledListItemElement);
10897 continue;
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,
10904 *editingHost);
10905 if (createNewDivElementResult.isErr()) {
10906 NS_WARNING(
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");
10916 return rv;
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");
10935 return rv;
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);
10944 return NS_OK;
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,
10955 ignoredError);
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");
10967 return result;
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(
10992 NS_SUCCEEDED(rv),
10993 "HTMLEditor::PrepareInlineStylesForCaret() failed, but ignored");
10997 RefPtr<Element> element = GetAbsolutelyPositionedSelectionContainer();
10998 if (!element) {
10999 if (NS_WARN_IF(Destroyed())) {
11000 return EditActionHandled(NS_ERROR_EDITOR_DESTROYED);
11002 NS_WARNING(
11003 "HTMLEditor::GetAbsolutelyPositionedSelectionContainer() returned "
11004 "nullptr");
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(
11033 *this,
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");
11048 return result;
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(
11073 NS_SUCCEEDED(rv),
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);
11084 NS_WARNING(
11085 "HTMLEditor::GetAbsolutelyPositionedSelectionContainer() returned "
11086 "nullptr");
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