1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=2 sw=2 et tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "HTMLEditor.h"
8 #include "HTMLEditorNestedClasses.h"
12 #include "AutoClonedRangeArray.h"
13 #include "CSSEditUtils.h"
14 #include "EditAction.h"
15 #include "EditorDOMPoint.h"
16 #include "EditorLineBreak.h"
17 #include "EditorUtils.h"
18 #include "HTMLEditHelpers.h"
19 #include "HTMLEditorInlines.h"
20 #include "HTMLEditUtils.h"
21 #include "WhiteSpaceVisibilityKeeper.h"
22 #include "WSRunScanner.h"
24 #include "ErrorList.h"
25 #include "mozilla/Assertions.h"
26 #include "mozilla/ContentIterator.h"
27 #include "mozilla/InternalMutationEvent.h"
28 #include "mozilla/Logging.h"
29 #include "mozilla/Maybe.h"
30 #include "mozilla/OwningNonNull.h"
31 #include "mozilla/SelectionState.h"
32 #include "mozilla/StaticPrefs_editor.h" // for StaticPrefs::editor_*
33 #include "mozilla/Unused.h"
34 #include "mozilla/dom/AncestorIterator.h"
35 #include "mozilla/dom/Element.h"
36 #include "mozilla/dom/ElementInlines.h" // for Element::IsContentEditablePlainTextOnly
37 #include "mozilla/dom/HTMLBRElement.h"
38 #include "mozilla/dom/Selection.h"
40 #include "nsComputedDOMStyle.h" // for nsComputedDOMStyle
41 #include "nsContentUtils.h"
44 #include "nsFrameSelection.h"
45 #include "nsGkAtoms.h"
46 #include "nsIContent.h"
50 #include "nsStringFwd.h"
51 #include "nsStyleConsts.h" // for StyleWhiteSpace
54 // NOTE: This file was split from:
55 // https://searchfox.org/mozilla-central/rev/c409dd9235c133ab41eba635f906aa16e050c197/editor/libeditor/HTMLEditSubActionHandler.cpp
60 using EmptyCheckOption
= HTMLEditUtils::EmptyCheckOption
;
61 using InvisibleWhiteSpaces
= HTMLEditUtils::InvisibleWhiteSpaces
;
62 using LeafNodeType
= HTMLEditUtils::LeafNodeType
;
63 using ScanLineBreak
= HTMLEditUtils::ScanLineBreak
;
64 using TableBoundary
= HTMLEditUtils::TableBoundary
;
65 using WalkTreeOption
= HTMLEditUtils::WalkTreeOption
;
67 static LazyLogModule
gOneLineMoverLog("AutoMoveOneLineHandler");
69 template Result
<CaretPoint
, nsresult
>
70 HTMLEditor::DeleteTextAndTextNodesWithTransaction(
71 const EditorDOMPoint
& aStartPoint
, const EditorDOMPoint
& aEndPoint
,
72 TreatEmptyTextNodes aTreatEmptyTextNodes
);
73 template Result
<CaretPoint
, nsresult
>
74 HTMLEditor::DeleteTextAndTextNodesWithTransaction(
75 const EditorDOMPointInText
& aStartPoint
,
76 const EditorDOMPointInText
& aEndPoint
,
77 TreatEmptyTextNodes aTreatEmptyTextNodes
);
79 /*****************************************************************************
80 * AutoDeleteRangesHandler
81 ****************************************************************************/
83 class MOZ_STACK_CLASS
HTMLEditor::AutoDeleteRangesHandler final
{
85 explicit AutoDeleteRangesHandler(
86 const AutoDeleteRangesHandler
* aParent
= nullptr)
88 mOriginalDirectionAndAmount(nsIEditor::eNone
),
89 mOriginalStripWrappers(nsIEditor::eNoStrip
) {}
92 * ComputeRangesToDelete() computes actual deletion ranges.
94 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT nsresult
ComputeRangesToDelete(
95 const HTMLEditor
& aHTMLEditor
, nsIEditor::EDirection aDirectionAndAmount
,
96 AutoClonedSelectionRangeArray
& aRangesToDelete
,
97 const Element
& aEditingHost
);
100 * Deletes content in or around aRangesToDelete.
101 * NOTE: This method creates SelectionBatcher. Therefore, each caller
102 * needs to check if the editor is still available even if this returns
105 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT Result
<EditActionResult
, nsresult
> Run(
106 HTMLEditor
& aHTMLEditor
, nsIEditor::EDirection aDirectionAndAmount
,
107 nsIEditor::EStripWrappers aStripWrappers
,
108 AutoClonedSelectionRangeArray
& aRangesToDelete
,
109 const Element
& aEditingHost
);
112 [[nodiscard
]] bool IsHandlingRecursively() const {
113 return mParent
!= nullptr;
116 [[nodiscard
]] bool CanFallbackToDeleteRangeWithTransaction(
117 const nsRange
& aRangeToDelete
) const {
118 return !IsHandlingRecursively() &&
119 (!aRangeToDelete
.Collapsed() ||
120 EditorBase::HowToHandleCollapsedRangeFor(
121 mOriginalDirectionAndAmount
) !=
122 EditorBase::HowToHandleCollapsedRange::Ignore
);
125 [[nodiscard
]] bool CanFallbackToDeleteRangesWithTransaction(
126 const AutoClonedSelectionRangeArray
& aRangesToDelete
) const {
127 return !IsHandlingRecursively() && !aRangesToDelete
.Ranges().IsEmpty() &&
128 (!aRangesToDelete
.IsCollapsed() ||
129 EditorBase::HowToHandleCollapsedRangeFor(
130 mOriginalDirectionAndAmount
) !=
131 EditorBase::HowToHandleCollapsedRange::Ignore
);
135 * HandleDeleteAroundCollapsedRanges() handles deletion with collapsed
136 * ranges. Callers must guarantee that this is called only when
137 * aRangesToDelete.IsCollapsed() returns true.
139 * @param aDirectionAndAmount Direction of the deletion.
140 * @param aStripWrappers Must be eStrip or eNoStrip.
141 * @param aRangesToDelete Ranges to delete. This `IsCollapsed()` must
143 * @param aWSRunScannerAtCaret Scanner instance which scanned from
145 * @param aScanFromCaretPointResult Scan result of aWSRunScannerAtCaret
146 * toward aDirectionAndAmount.
147 * @param aEditingHost The editing host.
149 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT Result
<EditActionResult
, nsresult
>
150 HandleDeleteAroundCollapsedRanges(
151 HTMLEditor
& aHTMLEditor
, nsIEditor::EDirection aDirectionAndAmount
,
152 nsIEditor::EStripWrappers aStripWrappers
,
153 AutoClonedSelectionRangeArray
& aRangesToDelete
,
154 const WSRunScanner
& aWSRunScannerAtCaret
,
155 const WSScanResult
& aScanFromCaretPointResult
,
156 const Element
& aEditingHost
);
157 nsresult
ComputeRangesToDeleteAroundCollapsedRanges(
158 const HTMLEditor
& aHTMLEditor
, nsIEditor::EDirection aDirectionAndAmount
,
159 AutoClonedSelectionRangeArray
& aRangesToDelete
,
160 const WSRunScanner
& aWSRunScannerAtCaret
,
161 const WSScanResult
& aScanFromCaretPointResult
,
162 const Element
& aEditingHost
) const;
165 * HandleDeleteNonCollapsedRanges() handles deletion with non-collapsed
166 * ranges. Callers must guarantee that this is called only when
167 * aRangesToDelete.IsCollapsed() returns false.
169 * @param aDirectionAndAmount Direction of the deletion.
170 * @param aStripWrappers Must be eStrip or eNoStrip.
171 * @param aRangesToDelete The ranges to delete.
172 * @param aSelectionWasCollapsed If the caller extended `Selection`
173 * from collapsed, set this to `Yes`.
174 * Otherwise, i.e., `Selection` is not
175 * collapsed from the beginning, set
177 * @param aEditingHost The editing host.
179 enum class SelectionWasCollapsed
{ Yes
, No
};
180 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT Result
<EditActionResult
, nsresult
>
181 HandleDeleteNonCollapsedRanges(HTMLEditor
& aHTMLEditor
,
182 nsIEditor::EDirection aDirectionAndAmount
,
183 nsIEditor::EStripWrappers aStripWrappers
,
184 AutoClonedSelectionRangeArray
& aRangesToDelete
,
185 SelectionWasCollapsed aSelectionWasCollapsed
,
186 const Element
& aEditingHost
);
187 nsresult
ComputeRangesToDeleteNonCollapsedRanges(
188 const HTMLEditor
& aHTMLEditor
, nsIEditor::EDirection aDirectionAndAmount
,
189 AutoClonedSelectionRangeArray
& aRangesToDelete
,
190 SelectionWasCollapsed aSelectionWasCollapsed
,
191 const Element
& aEditingHost
) const;
194 * Handle deletion of collapsed ranges in a text node.
196 * @param aDirectionAndAmount Must be eNext or ePrevious.
197 * @param aCaretPosition The position where caret is. This container
198 * must be a text node.
199 * @param aEditingHost The editing host.
201 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT Result
<CaretPoint
, nsresult
>
202 HandleDeleteTextAroundCollapsedRanges(
203 HTMLEditor
& aHTMLEditor
, nsIEditor::EDirection aDirectionAndAmount
,
204 AutoClonedSelectionRangeArray
& aRangesToDelete
,
205 const Element
& aEditingHost
);
206 nsresult
ComputeRangesToDeleteTextAroundCollapsedRanges(
207 nsIEditor::EDirection aDirectionAndAmount
,
208 AutoClonedSelectionRangeArray
& aRangesToDelete
) const;
211 * Handles deletion of collapsed selection at white-spaces in a text node.
213 * @param aDirectionAndAmount Direction of the deletion.
214 * @param aPointToDelete The point to delete. I.e., typically, caret
216 * @param aEditingHost The editing host.
218 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT Result
<CaretPoint
, nsresult
>
219 HandleDeleteCollapsedSelectionAtWhiteSpaces(
220 HTMLEditor
& aHTMLEditor
, nsIEditor::EDirection aDirectionAndAmount
,
221 const EditorDOMPoint
& aPointToDelete
, const Element
& aEditingHost
);
224 * Handle deletion of collapsed selection in a text node.
226 * @param aDirectionAndAmount Direction of the deletion.
227 * @param aRangesToDelete Computed selection ranges to delete.
228 * @param aPointAtDeletingChar The visible char position which you want to
230 * @param aEditingHost The editing host.
232 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT Result
<CaretPoint
, nsresult
>
233 HandleDeleteCollapsedSelectionAtVisibleChar(
234 HTMLEditor
& aHTMLEditor
, nsIEditor::EDirection aDirectionAndAmount
,
235 AutoClonedSelectionRangeArray
& aRangesToDelete
,
236 const EditorDOMPoint
& aPointAtDeletingChar
, const Element
& aEditingHost
);
239 * Handle deletion of atomic elements like <br>, <hr>, <img>, <input>, etc and
240 * data nodes except text node (e.g., comment node). Note that don't call this
241 * directly with `<hr>` element.
243 * @param aAtomicContent The atomic content to be deleted.
244 * @param aCaretPoint The caret point (i.e., selection start or
246 * @param aWSRunScannerAtCaret WSRunScanner instance which was initialized
247 * with the caret point.
248 * @param aEditingHost The editing host.
250 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT Result
<CaretPoint
, nsresult
>
251 HandleDeleteAtomicContent(HTMLEditor
& aHTMLEditor
, nsIContent
& aAtomicContent
,
252 const EditorDOMPoint
& aCaretPoint
,
253 const WSRunScanner
& aWSRunScannerAtCaret
,
254 const Element
& aEditingHost
);
255 nsresult
ComputeRangesToDeleteAtomicContent(
256 const nsIContent
& aAtomicContent
,
257 AutoClonedSelectionRangeArray
& aRangesToDelete
) const;
260 * GetAtomicContnetToDelete() returns better content that is deletion of
261 * atomic element. If aScanFromCaretPointResult is special, since this
262 * point may not be editable, we look for better point to remove atomic
265 * @param aDirectionAndAmount Direction of the deletion.
266 * @param aWSRunScannerAtCaret WSRunScanner instance which was
267 * initialized with the caret point.
268 * @param aScanFromCaretPointResult Scan result of aWSRunScannerAtCaret
269 * toward aDirectionAndAmount.
271 static nsIContent
* GetAtomicContentToDelete(
272 nsIEditor::EDirection aDirectionAndAmount
,
273 const WSRunScanner
& aWSRunScannerAtCaret
,
274 const WSScanResult
& aScanFromCaretPointResult
) MOZ_NONNULL_RETURN
;
277 * HandleDeleteAtOtherBlockBoundary() handles deletion at other block boundary
278 * (i.e., immediately before or after a block). If this does not join blocks,
279 * `Run()` may be called recursively with creating another instance.
281 * @param aDirectionAndAmount Direction of the deletion.
282 * @param aStripWrappers Must be eStrip or eNoStrip.
283 * @param aOtherBlockElement The block element which follows the caret or
284 * is followed by caret.
285 * @param aCaretPoint The caret point (i.e., selection start or
287 * @param aWSRunScannerAtCaret WSRunScanner instance which was initialized
288 * with the caret point.
289 * @param aRangesToDelete Ranges to delete of the caller. This should
290 * be collapsed and the point should match with
292 * @param aEditingHost The editing host.
294 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT Result
<EditActionResult
, nsresult
>
295 HandleDeleteAtOtherBlockBoundary(
296 HTMLEditor
& aHTMLEditor
, nsIEditor::EDirection aDirectionAndAmount
,
297 nsIEditor::EStripWrappers aStripWrappers
, Element
& aOtherBlockElement
,
298 const EditorDOMPoint
& aCaretPoint
, WSRunScanner
& aWSRunScannerAtCaret
,
299 AutoClonedSelectionRangeArray
& aRangesToDelete
,
300 const Element
& aEditingHost
);
303 * ExtendOrShrinkRangeToDelete() extends aRangeToDelete if there are
304 * an invisible <br> element and/or some parent empty elements.
306 * @param aLimitersAndCaretData The frame selection data.
307 * @param aRangeToDelete The range to be extended for deletion. This
308 * must not be collapsed, must be positioned.
310 template <typename EditorDOMRangeType
>
311 Result
<EditorRawDOMRange
, nsresult
> ExtendOrShrinkRangeToDelete(
312 const HTMLEditor
& aHTMLEditor
,
313 const LimitersAndCaretData
& aLimitersAndCaretData
,
314 const EditorDOMRangeType
& aRangeToDelete
) const;
317 * Extend the start boundary of aRangeToDelete to contain ancestor inline
318 * elements which will be empty once the content in aRangeToDelete is removed
321 * NOTE: This is designed for deleting inline elements which become empty if
322 * aRangeToDelete which crosses a block boundary of right block child.
323 * Therefore, you may need to improve this method if you want to use this in
326 * @param aRangeToDelete [in/out] The range to delete. This start
327 * boundary may be modified.
328 * @param aEditingHost The editing host.
329 * @return true if aRangeToDelete is modified.
330 * false if aRangeToDelete is not modified.
331 * error if aRangeToDelete gets unexpected
334 static Result
<bool, nsresult
>
335 ExtendRangeToContainAncestorInlineElementsAtStart(
336 nsRange
& aRangeToDelete
, const Element
& aEditingHost
);
339 * A helper method for ExtendOrShrinkRangeToDelete(). This returns shrunken
340 * range if aRangeToDelete selects all over list elements which have some list
341 * item elements to avoid to delete all list items from the list element.
343 MOZ_NEVER_INLINE_DEBUG
static EditorRawDOMRange
344 GetRangeToAvoidDeletingAllListItemsIfSelectingAllOverListElements(
345 const EditorRawDOMRange
& aRangeToDelete
);
348 * DeleteUnnecessaryNodes() removes unnecessary nodes around aRange.
350 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT nsresult
351 DeleteUnnecessaryNodes(HTMLEditor
& aHTMLEditor
, const EditorDOMRange
& aRange
,
352 const Element
& aEditingHost
);
355 * If aContent is a text node that contains only collapsed white-space or
356 * empty and editable.
358 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT nsresult
359 DeleteNodeIfInvisibleAndEditableTextNode(HTMLEditor
& aHTMLEditor
,
360 nsIContent
& aContent
);
363 * DeleteParentBlocksIfEmpty() removes parent block elements if they
364 * don't have visible contents. Note that due performance issue of
365 * WhiteSpaceVisibilityKeeper, this call may be expensive. And also note that
366 * this removes a empty block with a transaction. So, please make sure that
367 * you've already created `AutoPlaceholderBatch`.
369 * @param aPoint The point whether this method climbing up the DOM
370 * tree to remove empty parent blocks.
371 * @return NS_OK if one or more empty block parents are deleted.
372 * NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND if the point is
373 * not in empty block.
374 * Or NS_ERROR_* if something unexpected occurs.
376 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT nsresult
377 DeleteParentBlocksWithTransactionIfEmpty(HTMLEditor
& aHTMLEditor
,
378 const EditorDOMPoint
& aPoint
);
380 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT Result
<CaretPoint
, nsresult
>
381 FallbackToDeleteRangeWithTransaction(HTMLEditor
& aHTMLEditor
,
382 nsRange
& aRangeToDelete
) const {
383 MOZ_ASSERT(aHTMLEditor
.IsEditActionDataAvailable());
384 MOZ_ASSERT(CanFallbackToDeleteRangeWithTransaction(aRangeToDelete
));
385 Result
<CaretPoint
, nsresult
> caretPointOrError
=
386 aHTMLEditor
.DeleteRangeWithTransaction(mOriginalDirectionAndAmount
,
387 mOriginalStripWrappers
,
389 NS_WARNING_ASSERTION(caretPointOrError
.isOk(),
390 "EditorBase::DeleteRangeWithTransaction() failed");
391 return caretPointOrError
;
394 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT Result
<CaretPoint
, nsresult
>
395 FallbackToDeleteRangesWithTransaction(
396 HTMLEditor
& aHTMLEditor
,
397 AutoClonedSelectionRangeArray
& aRangesToDelete
) const {
398 MOZ_ASSERT(aHTMLEditor
.IsEditActionDataAvailable());
399 MOZ_ASSERT(CanFallbackToDeleteRangesWithTransaction(aRangesToDelete
));
400 Result
<CaretPoint
, nsresult
> caretPointOrError
=
401 aHTMLEditor
.DeleteRangesWithTransaction(mOriginalDirectionAndAmount
,
402 mOriginalStripWrappers
,
404 NS_WARNING_ASSERTION(caretPointOrError
.isOk(),
405 "HTMLEditor::DeleteRangesWithTransaction() failed");
406 return caretPointOrError
;
410 * Compute target range(s) which will be called by
411 * `EditorBase::DeleteRangeWithTransaction()` or
412 * `HTMLEditor::DeleteRangesWithTransaction()`.
413 * TODO: We should not use it for consistency with each deletion handler
414 * in this and nested classes.
416 nsresult
ComputeRangeToDeleteRangeWithTransaction(
417 const HTMLEditor
& aHTMLEditor
, nsIEditor::EDirection aDirectionAndAmount
,
418 nsRange
& aRange
, const Element
& aEditingHost
) const;
419 nsresult
ComputeRangesToDeleteRangesWithTransaction(
420 const HTMLEditor
& aHTMLEditor
, nsIEditor::EDirection aDirectionAndAmount
,
421 AutoClonedSelectionRangeArray
& aRangesToDelete
,
422 const Element
& aEditingHost
) const {
423 MOZ_ASSERT(!aRangesToDelete
.Ranges().IsEmpty());
424 const EditorBase::HowToHandleCollapsedRange howToHandleCollapsedRange
=
425 EditorBase::HowToHandleCollapsedRangeFor(aDirectionAndAmount
);
426 if (NS_WARN_IF(aRangesToDelete
.IsCollapsed() &&
427 howToHandleCollapsedRange
==
428 EditorBase::HowToHandleCollapsedRange::Ignore
)) {
429 return NS_ERROR_FAILURE
;
432 for (const OwningNonNull
<nsRange
>& range
: aRangesToDelete
.Ranges()) {
433 if (range
->Collapsed()) {
436 nsresult rv
= ComputeRangeToDeleteRangeWithTransaction(
437 aHTMLEditor
, aDirectionAndAmount
, range
, aEditingHost
);
440 "AutoDeleteRangesHandler::ComputeRangeToDeleteRangeWithTransaction("
448 nsresult
FallbackToComputeRangeToDeleteRangeWithTransaction(
449 const HTMLEditor
& aHTMLEditor
, nsRange
& aRangeToDelete
,
450 const Element
& aEditingHost
) const {
451 MOZ_ASSERT(aHTMLEditor
.IsEditActionDataAvailable());
452 MOZ_ASSERT(CanFallbackToDeleteRangeWithTransaction(aRangeToDelete
));
453 nsresult rv
= ComputeRangeToDeleteRangeWithTransaction(
454 aHTMLEditor
, mOriginalDirectionAndAmount
, aRangeToDelete
, aEditingHost
);
455 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
456 "AutoDeleteRangesHandler::"
457 "ComputeRangeToDeleteRangeWithTransaction() failed");
460 nsresult
FallbackToComputeRangesToDeleteRangesWithTransaction(
461 const HTMLEditor
& aHTMLEditor
,
462 AutoClonedSelectionRangeArray
& aRangesToDelete
,
463 const Element
& aEditingHost
) const {
464 MOZ_ASSERT(aHTMLEditor
.IsEditActionDataAvailable());
465 MOZ_ASSERT(CanFallbackToDeleteRangesWithTransaction(aRangesToDelete
));
466 nsresult rv
= ComputeRangesToDeleteRangesWithTransaction(
467 aHTMLEditor
, mOriginalDirectionAndAmount
, aRangesToDelete
,
469 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
470 "AutoDeleteRangesHandler::"
471 "ComputeRangesToDeleteRangesWithTransaction() failed");
475 class MOZ_STACK_CLASS AutoBlockElementsJoiner final
{
477 AutoBlockElementsJoiner() = delete;
478 explicit AutoBlockElementsJoiner(
479 AutoDeleteRangesHandler
& aDeleteRangesHandler
)
480 : mDeleteRangesHandler(&aDeleteRangesHandler
),
481 mDeleteRangesHandlerConst(aDeleteRangesHandler
) {}
482 explicit AutoBlockElementsJoiner(
483 const AutoDeleteRangesHandler
& aDeleteRangesHandler
)
484 : mDeleteRangesHandler(nullptr),
485 mDeleteRangesHandlerConst(aDeleteRangesHandler
) {}
488 * PrepareToDeleteAtCurrentBlockBoundary() considers left content and right
489 * content which are joined for handling deletion at current block boundary
490 * (i.e., at start or end of the current block).
492 * @param aHTMLEditor The HTML editor.
493 * @param aDirectionAndAmount Direction of the deletion.
494 * @param aCurrentBlockElement The current block element.
495 * @param aCaretPoint The caret point (i.e., selection start
497 * @param aEditingHost The editing host.
498 * @return true if can continue to handle the
501 bool PrepareToDeleteAtCurrentBlockBoundary(
502 const HTMLEditor
& aHTMLEditor
,
503 nsIEditor::EDirection aDirectionAndAmount
,
504 Element
& aCurrentBlockElement
, const EditorDOMPoint
& aCaretPoint
,
505 const Element
& aEditingHost
);
508 * PrepareToDeleteAtOtherBlockBoundary() considers left content and right
509 * content which are joined for handling deletion at other block boundary
510 * (i.e., immediately before or after a block).
512 * @param aHTMLEditor The HTML editor.
513 * @param aDirectionAndAmount Direction of the deletion.
514 * @param aOtherBlockElement The block element which follows the
515 * caret or is followed by caret.
516 * @param aCaretPoint The caret point (i.e., selection start
518 * @param aWSRunScannerAtCaret WSRunScanner instance which was
519 * initialized with the caret point.
520 * @return true if can continue to handle the
523 bool PrepareToDeleteAtOtherBlockBoundary(
524 const HTMLEditor
& aHTMLEditor
,
525 nsIEditor::EDirection aDirectionAndAmount
, Element
& aOtherBlockElement
,
526 const EditorDOMPoint
& aCaretPoint
,
527 const WSRunScanner
& aWSRunScannerAtCaret
);
530 * PrepareToDeleteNonCollapsedRange() considers left block element and
531 * right block element which are inclusive ancestor block element of
532 * start and end container of aRangeToDelete
534 * @param aHTMLEditor The HTML editor.
535 * @param aRangeToDelete The range to delete. Must not be
537 * @param aEditingHost The editing host.
538 * @return true if can continue to handle the
541 bool PrepareToDeleteNonCollapsedRange(const HTMLEditor
& aHTMLEditor
,
542 const nsRange
& aRangeToDelete
,
543 const Element
& aEditingHost
);
546 * Run() executes the joining.
548 * @param aHTMLEditor The HTML editor.
549 * @param aDirectionAndAmount Direction of the deletion.
550 * @param aStripWrappers Must be eStrip or eNoStrip.
551 * @param aCaretPoint The caret point (i.e., selection start
553 * @param aRangeToDelete The range to delete. This should be
554 * collapsed and match with aCaretPoint.
556 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT Result
<EditActionResult
, nsresult
> Run(
557 HTMLEditor
& aHTMLEditor
, nsIEditor::EDirection aDirectionAndAmount
,
558 nsIEditor::EStripWrappers aStripWrappers
,
559 const EditorDOMPoint
& aCaretPoint
, nsRange
& aRangeToDelete
,
560 const Element
& aEditingHost
) {
562 case Mode::JoinCurrentBlock
: {
563 Result
<EditActionResult
, nsresult
> result
=
564 HandleDeleteAtCurrentBlockBoundary(
565 aHTMLEditor
, aDirectionAndAmount
, aCaretPoint
, aEditingHost
);
566 NS_WARNING_ASSERTION(result
.isOk(),
567 "AutoBlockElementsJoiner::"
568 "HandleDeleteAtCurrentBlockBoundary() failed");
571 case Mode::JoinOtherBlock
: {
572 Result
<EditActionResult
, nsresult
> result
=
573 HandleDeleteAtOtherBlockBoundary(aHTMLEditor
, aDirectionAndAmount
,
574 aStripWrappers
, aCaretPoint
,
575 aRangeToDelete
, aEditingHost
);
576 NS_WARNING_ASSERTION(result
.isOk(),
577 "AutoBlockElementsJoiner::"
578 "HandleDeleteAtOtherBlockBoundary() failed");
581 case Mode::DeleteBRElement
:
582 case Mode::DeletePrecedingBRElementOfBlock
:
583 case Mode::DeletePrecedingPreformattedLineBreak
: {
584 Result
<EditActionResult
, nsresult
> result
= HandleDeleteLineBreak(
585 aHTMLEditor
, aDirectionAndAmount
, aCaretPoint
, aEditingHost
);
586 NS_WARNING_ASSERTION(
588 "AutoBlockElementsJoiner::HandleDeleteLineBreak() failed");
591 case Mode::JoinBlocksInSameParent
:
592 case Mode::DeleteContentInRange
:
593 case Mode::DeleteNonCollapsedRange
:
594 case Mode::DeletePrecedingLinesAndContentInRange
:
595 MOZ_ASSERT_UNREACHABLE(
596 "This mode should be handled in the other Run()");
597 return Err(NS_ERROR_UNEXPECTED
);
598 case Mode::NotInitialized
:
599 return EditActionResult::IgnoredResult();
601 return Err(NS_ERROR_NOT_INITIALIZED
);
604 nsresult
ComputeRangeToDelete(const HTMLEditor
& aHTMLEditor
,
605 nsIEditor::EDirection aDirectionAndAmount
,
606 const EditorDOMPoint
& aCaretPoint
,
607 nsRange
& aRangeToDelete
,
608 const Element
& aEditingHost
) const {
610 case Mode::JoinCurrentBlock
: {
611 nsresult rv
= ComputeRangeToDeleteAtCurrentBlockBoundary(
612 aHTMLEditor
, aCaretPoint
, aRangeToDelete
, aEditingHost
);
613 NS_WARNING_ASSERTION(
615 "AutoBlockElementsJoiner::"
616 "ComputeRangeToDeleteAtCurrentBlockBoundary() failed");
619 case Mode::JoinOtherBlock
: {
620 nsresult rv
= ComputeRangeToDeleteAtOtherBlockBoundary(
621 aHTMLEditor
, aDirectionAndAmount
, aCaretPoint
, aRangeToDelete
,
623 NS_WARNING_ASSERTION(
625 "AutoBlockElementsJoiner::"
626 "ComputeRangeToDeleteAtOtherBlockBoundary() failed");
629 case Mode::DeleteBRElement
:
630 case Mode::DeletePrecedingBRElementOfBlock
:
631 case Mode::DeletePrecedingPreformattedLineBreak
: {
632 nsresult rv
= ComputeRangeToDeleteLineBreak(
633 aHTMLEditor
, aRangeToDelete
, aEditingHost
,
634 ComputeRangeFor::GetTargetRanges
);
635 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
636 "AutoBlockElementsJoiner::"
637 "ComputeRangeToDeleteLineBreak() failed");
640 case Mode::JoinBlocksInSameParent
:
641 case Mode::DeleteContentInRange
:
642 case Mode::DeleteNonCollapsedRange
:
643 case Mode::DeletePrecedingLinesAndContentInRange
:
644 MOZ_ASSERT_UNREACHABLE(
645 "This mode should be handled in the other "
646 "ComputeRangesToDelete()");
647 return NS_ERROR_UNEXPECTED
;
648 case Mode::NotInitialized
:
651 return NS_ERROR_NOT_IMPLEMENTED
;
655 * Run() executes the joining.
657 * @param aHTMLEditor The HTML editor.
658 * @param aLimitersAndCaretData The data copied from nsFrameSelection.
659 * @param aDirectionAndAmount Direction of the deletion.
660 * @param aStripWrappers Whether delete or keep new empty
662 * @param aRangeToDelete The range to delete. Must not be
664 * @param aSelectionWasCollapsed Whether selection was or was not
665 * collapsed when starting to handle
668 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT Result
<EditActionResult
, nsresult
> Run(
669 HTMLEditor
& aHTMLEditor
,
670 const LimitersAndCaretData
& aLimitersAndCaretData
,
671 nsIEditor::EDirection aDirectionAndAmount
,
672 nsIEditor::EStripWrappers aStripWrappers
, nsRange
& aRangeToDelete
,
673 AutoDeleteRangesHandler::SelectionWasCollapsed aSelectionWasCollapsed
,
674 const Element
& aEditingHost
) {
676 case Mode::JoinCurrentBlock
:
677 case Mode::JoinOtherBlock
:
678 case Mode::DeleteBRElement
:
679 case Mode::DeletePrecedingBRElementOfBlock
:
680 case Mode::DeletePrecedingPreformattedLineBreak
:
681 MOZ_ASSERT_UNREACHABLE(
682 "This mode should be handled in the other Run()");
683 return Err(NS_ERROR_UNEXPECTED
);
684 case Mode::JoinBlocksInSameParent
: {
685 Result
<EditActionResult
, nsresult
> result
=
686 JoinBlockElementsInSameParent(
687 aHTMLEditor
, aLimitersAndCaretData
, aDirectionAndAmount
,
688 aStripWrappers
, aRangeToDelete
, aSelectionWasCollapsed
,
690 NS_WARNING_ASSERTION(result
.isOk(),
691 "AutoBlockElementsJoiner::"
692 "JoinBlockElementsInSameParent() failed");
695 case Mode::DeleteContentInRange
: {
696 Result
<EditActionResult
, nsresult
> result
= DeleteContentInRange(
697 aHTMLEditor
, aLimitersAndCaretData
, aDirectionAndAmount
,
698 aStripWrappers
, aRangeToDelete
, aEditingHost
);
699 NS_WARNING_ASSERTION(
701 "AutoBlockElementsJoiner::DeleteContentInRange() failed");
704 case Mode::DeleteNonCollapsedRange
:
705 case Mode::DeletePrecedingLinesAndContentInRange
: {
706 Result
<EditActionResult
, nsresult
> result
=
707 HandleDeleteNonCollapsedRange(
708 aHTMLEditor
, aDirectionAndAmount
, aStripWrappers
,
709 aRangeToDelete
, aSelectionWasCollapsed
, aEditingHost
);
710 NS_WARNING_ASSERTION(result
.isOk(),
711 "AutoBlockElementsJoiner::"
712 "HandleDeleteNonCollapsedRange() failed");
715 case Mode::NotInitialized
:
716 MOZ_ASSERT_UNREACHABLE(
717 "Call Run() after calling a preparation method");
718 return EditActionResult::IgnoredResult();
720 return Err(NS_ERROR_NOT_INITIALIZED
);
723 nsresult
ComputeRangeToDelete(
724 const HTMLEditor
& aHTMLEditor
,
725 const AutoClonedSelectionRangeArray
& aRangesToDelete
,
726 nsIEditor::EDirection aDirectionAndAmount
, nsRange
& aRangeToDelete
,
727 AutoDeleteRangesHandler::SelectionWasCollapsed aSelectionWasCollapsed
,
728 const Element
& aEditingHost
) const {
730 case Mode::JoinCurrentBlock
:
731 case Mode::JoinOtherBlock
:
732 case Mode::DeleteBRElement
:
733 case Mode::DeletePrecedingBRElementOfBlock
:
734 case Mode::DeletePrecedingPreformattedLineBreak
:
735 MOZ_ASSERT_UNREACHABLE(
736 "This mode should be handled in the other "
737 "ComputeRangesToDelete()");
738 return NS_ERROR_UNEXPECTED
;
739 case Mode::JoinBlocksInSameParent
: {
740 nsresult rv
= ComputeRangeToJoinBlockElementsInSameParent(
741 aHTMLEditor
, aDirectionAndAmount
, aRangeToDelete
, aEditingHost
);
742 NS_WARNING_ASSERTION(
744 "AutoBlockElementsJoiner::"
745 "ComputeRangesToJoinBlockElementsInSameParent() failed");
748 case Mode::DeleteContentInRange
: {
749 nsresult rv
= ComputeRangeToDeleteContentInRange(
750 aHTMLEditor
, aDirectionAndAmount
, aRangeToDelete
, aEditingHost
);
751 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
752 "AutoBlockElementsJoiner::"
753 "ComputeRangesToDeleteContentInRanges() failed");
756 case Mode::DeleteNonCollapsedRange
:
757 case Mode::DeletePrecedingLinesAndContentInRange
: {
758 nsresult rv
= ComputeRangeToDeleteNonCollapsedRange(
759 aHTMLEditor
, aDirectionAndAmount
, aRangeToDelete
,
760 aSelectionWasCollapsed
, aEditingHost
);
761 NS_WARNING_ASSERTION(
763 "AutoBlockElementsJoiner::"
764 "ComputeRangesToDeleteNonCollapsedRanges() failed");
767 case Mode::NotInitialized
:
768 MOZ_ASSERT_UNREACHABLE(
769 "Call ComputeRangesToDelete() after calling a preparation "
771 return NS_ERROR_NOT_INITIALIZED
;
773 return NS_ERROR_NOT_INITIALIZED
;
776 nsIContent
* GetLeafContentInOtherBlockElement() const {
777 MOZ_ASSERT(mMode
== Mode::JoinOtherBlock
);
778 return mLeafContentInOtherBlock
;
782 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT Result
<EditActionResult
, nsresult
>
783 HandleDeleteAtCurrentBlockBoundary(
784 HTMLEditor
& aHTMLEditor
, nsIEditor::EDirection aDirectionAndAmount
,
785 const EditorDOMPoint
& aCaretPoint
, const Element
& aEditingHost
);
786 nsresult
ComputeRangeToDeleteAtCurrentBlockBoundary(
787 const HTMLEditor
& aHTMLEditor
, const EditorDOMPoint
& aCaretPoint
,
788 nsRange
& aRangeToDelete
, const Element
& aEditingHost
) const;
789 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT Result
<EditActionResult
, nsresult
>
790 HandleDeleteAtOtherBlockBoundary(HTMLEditor
& aHTMLEditor
,
791 nsIEditor::EDirection aDirectionAndAmount
,
792 nsIEditor::EStripWrappers aStripWrappers
,
793 const EditorDOMPoint
& aCaretPoint
,
794 nsRange
& aRangeToDelete
,
795 const Element
& aEditingHost
);
796 // FYI: This method may modify selection, but it won't cause running
797 // script because of `AutoHideSelectionChanges` which blocks
798 // selection change listeners and the selection change event
800 MOZ_CAN_RUN_SCRIPT_BOUNDARY nsresult
801 ComputeRangeToDeleteAtOtherBlockBoundary(
802 const HTMLEditor
& aHTMLEditor
,
803 nsIEditor::EDirection aDirectionAndAmount
,
804 const EditorDOMPoint
& aCaretPoint
, nsRange
& aRangeToDelete
,
805 const Element
& aEditingHost
) const;
806 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT Result
<EditActionResult
, nsresult
>
807 JoinBlockElementsInSameParent(
808 HTMLEditor
& aHTMLEditor
,
809 const LimitersAndCaretData
& aLimitersAndCaretData
,
810 nsIEditor::EDirection aDirectionAndAmount
,
811 nsIEditor::EStripWrappers aStripWrappers
, nsRange
& aRangeToDelete
,
812 AutoDeleteRangesHandler::SelectionWasCollapsed aSelectionWasCollapsed
,
813 const Element
& aEditingHost
);
814 nsresult
ComputeRangeToJoinBlockElementsInSameParent(
815 const HTMLEditor
& aHTMLEditor
,
816 nsIEditor::EDirection aDirectionAndAmount
, nsRange
& aRangeToDelete
,
817 const Element
& aEditingHost
) const;
818 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT Result
<EditActionResult
, nsresult
>
819 HandleDeleteLineBreak(HTMLEditor
& aHTMLEditor
,
820 nsIEditor::EDirection aDirectionAndAmount
,
821 const EditorDOMPoint
& aCaretPoint
,
822 const Element
& aEditingHost
);
823 enum class ComputeRangeFor
: bool { GetTargetRanges
, ToDeleteTheRange
};
824 nsresult
ComputeRangeToDeleteLineBreak(
825 const HTMLEditor
& aHTMLEditor
, nsRange
& aRangeToDelete
,
826 const Element
& aEditingHost
, ComputeRangeFor aComputeRangeFor
) const;
827 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT Result
<EditActionResult
, nsresult
>
828 DeleteContentInRange(HTMLEditor
& aHTMLEditor
,
829 const LimitersAndCaretData
& aLimitersAndCaretData
,
830 nsIEditor::EDirection aDirectionAndAmount
,
831 nsIEditor::EStripWrappers aStripWrappers
,
832 nsRange
& aRangeToDelete
, const Element
& aEditingHost
);
833 nsresult
ComputeRangeToDeleteContentInRange(
834 const HTMLEditor
& aHTMLEditor
,
835 nsIEditor::EDirection aDirectionAndAmount
, nsRange
& aRange
,
836 const Element
& aEditingHost
) const;
837 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT Result
<EditActionResult
, nsresult
>
838 HandleDeleteNonCollapsedRange(
839 HTMLEditor
& aHTMLEditor
, nsIEditor::EDirection aDirectionAndAmount
,
840 nsIEditor::EStripWrappers aStripWrappers
, nsRange
& aRangeToDelete
,
841 AutoDeleteRangesHandler::SelectionWasCollapsed aSelectionWasCollapsed
,
842 const Element
& aEditingHost
);
843 nsresult
ComputeRangeToDeleteNonCollapsedRange(
844 const HTMLEditor
& aHTMLEditor
,
845 nsIEditor::EDirection aDirectionAndAmount
, nsRange
& aRangeToDelete
,
846 AutoDeleteRangesHandler::SelectionWasCollapsed aSelectionWasCollapsed
,
847 const Element
& aEditingHost
) const;
850 * JoinNodesDeepWithTransaction() joins aLeftNode and aRightNode "deeply".
851 * First, they are joined simply, then, new right node is assumed as the
852 * child at length of the left node before joined and new left node is
853 * assumed as its previous sibling. Then, they will be joined again.
854 * And then, these steps are repeated.
856 * @param aLeftContent The node which will be removed form the tree.
857 * @param aRightContent The node which will be inserted the contents of
859 * @return The point of the first child of the last right
860 * node. The result is always set if this succeeded.
862 MOZ_CAN_RUN_SCRIPT Result
<EditorDOMPoint
, nsresult
>
863 JoinNodesDeepWithTransaction(HTMLEditor
& aHTMLEditor
,
864 nsIContent
& aLeftContent
,
865 nsIContent
& aRightContent
);
867 enum class PutCaretTo
: bool { StartOfRange
, EndOfRange
};
870 * DeleteNodesEntirelyInRangeButKeepTableStructure() removes each node in
871 * aArrayOfContent. However, if some nodes are part of a table, removes all
872 * children of them instead. I.e., this does not make damage to table
873 * structure at the range, but may remove table entirely if it's in the
876 MOZ_CAN_RUN_SCRIPT Result
<DeleteRangeResult
, nsresult
>
877 DeleteNodesEntirelyInRangeButKeepTableStructure(
878 HTMLEditor
& aHTMLEditor
,
879 const nsTArray
<OwningNonNull
<nsIContent
>>& aArrayOfContent
,
880 PutCaretTo aPutCaretTo
);
881 bool NeedsToJoinNodesAfterDeleteNodesEntirelyInRangeButKeepTableStructure(
882 const HTMLEditor
& aHTMLEditor
,
883 const nsTArray
<OwningNonNull
<nsIContent
>>& aArrayOfContents
,
884 AutoDeleteRangesHandler::SelectionWasCollapsed aSelectionWasCollapsed
)
886 Result
<bool, nsresult
>
887 ComputeRangeToDeleteNodesEntirelyInRangeButKeepTableStructure(
888 const HTMLEditor
& aHTMLEditor
, nsRange
& aRange
,
889 AutoDeleteRangesHandler::SelectionWasCollapsed aSelectionWasCollapsed
)
893 * DeleteContentButKeepTableStructure() removes aContent if it's an element
894 * which is part of a table structure. If it's a part of table structure,
895 * removes its all children recursively. I.e., this may delete all of a
896 * table, but won't break table structure partially.
898 * @param aContent The content which or whose all children should
901 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT Result
<DeleteRangeResult
, nsresult
>
902 DeleteContentButKeepTableStructure(HTMLEditor
& aHTMLEditor
,
903 nsIContent
& aContent
);
906 * DeleteTextAtStartAndEndOfRange() removes text if start and/or end of
907 * aRange is in a text node.
909 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT Result
<DeleteRangeResult
, nsresult
>
910 DeleteTextAtStartAndEndOfRange(HTMLEditor
& aHTMLEditor
, nsRange
& aRange
,
911 PutCaretTo aPutCaretTo
);
914 * Return a block element which is an inclusive ancestor of the container of
915 * aPoint if aPoint is start of ancestor blocks. For example, if `<div
916 * id=div1>abc<div id=div2><div id=div3>[]def</div></div></div>`, return
919 template <typename EditorDOMPointType
>
920 static Result
<Element
*, nsresult
>
921 GetMostDistantBlockAncestorIfPointIsStartAtBlock(
922 const EditorDOMPointType
& aPoint
, const Element
& aEditingHost
,
923 const Element
* aAncestorLimiter
= nullptr);
926 * Extend aRangeToDelete to contain new empty inline ancestors and contain
927 * an invisible <br> element before right child block which causes an empty
928 * line but the range starts after it.
930 void ExtendRangeToDeleteNonCollapsedRange(
931 const HTMLEditor
& aHTMLEditor
, nsRange
& aRangeToDelete
,
932 const Element
& aEditingHost
, ComputeRangeFor aComputeRangeFor
) const;
934 class MOZ_STACK_CLASS AutoInclusiveAncestorBlockElementsJoiner final
{
936 AutoInclusiveAncestorBlockElementsJoiner() = delete;
937 AutoInclusiveAncestorBlockElementsJoiner(
938 nsIContent
& aInclusiveDescendantOfLeftBlockElement
,
939 nsIContent
& aInclusiveDescendantOfRightBlockElement
)
940 : mInclusiveDescendantOfLeftBlockElement(
941 aInclusiveDescendantOfLeftBlockElement
),
942 mInclusiveDescendantOfRightBlockElement(
943 aInclusiveDescendantOfRightBlockElement
),
944 mCanJoinBlocks(false),
945 mFallbackToDeleteLeafContent(false) {}
947 bool IsSet() const { return mLeftBlockElement
&& mRightBlockElement
; }
948 bool IsSameBlockElement() const {
949 return mLeftBlockElement
&& mLeftBlockElement
== mRightBlockElement
;
953 * Prepare for joining inclusive ancestor block elements. When this
954 * returns false, the deletion should be canceled.
956 Result
<bool, nsresult
> Prepare(const HTMLEditor
& aHTMLEditor
,
957 const Element
& aEditingHost
);
960 * When this returns true, this can join the blocks with `Run()`.
962 bool CanJoinBlocks() const { return mCanJoinBlocks
; }
965 * When this returns true, `Run()` must return "ignored" so that
966 * caller can skip calling `Run()`. This is available only when
967 * `CanJoinBlocks()` returns `true`.
968 * TODO: This should be merged into `CanJoinBlocks()` in the future.
970 bool ShouldDeleteLeafContentInstead() const {
971 MOZ_ASSERT(CanJoinBlocks());
972 return mFallbackToDeleteLeafContent
;
976 * ComputeRangesToDelete() extends aRangeToDelete includes the element
977 * boundaries between joining blocks. If they won't be joined, this
978 * collapses the range to aCaretPoint.
980 nsresult
ComputeRangeToDelete(const HTMLEditor
& aHTMLEditor
,
981 const EditorDOMPoint
& aCaretPoint
,
982 nsRange
& aRangeToDelete
) const;
985 * Join inclusive ancestor block elements which are found by preceding
987 * The right element is always joined to the left element.
988 * If the elements are the same type and not nested within each other,
989 * JoinEditableNodesWithTransaction() is called (example, joining two
990 * list items together into one).
991 * If the elements are not the same type, or one is a descendant of the
992 * other, we instead destroy the right block placing its children into
995 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT Result
<DeleteRangeResult
, nsresult
> Run(
996 HTMLEditor
& aHTMLEditor
, const Element
& aEditingHost
);
1000 * This method returns true when
1001 * `MergeFirstLineOfRightBlockElementIntoDescendantLeftBlockElement()`,
1002 * `MergeFirstLineOfRightBlockElementIntoAncestorLeftBlockElement()` and
1003 * `MergeFirstLineOfRightBlockElementIntoLeftBlockElement()` handle it
1004 * with the `if` block of their main blocks.
1006 bool CanMergeLeftAndRightBlockElements() const {
1010 // `MergeFirstLineOfRightBlockElementIntoDescendantLeftBlockElement()`
1011 if (mPointContainingTheOtherBlockElement
.GetContainer() ==
1012 mRightBlockElement
) {
1013 return mNewListElementTagNameOfRightListElement
.isSome();
1015 // `MergeFirstLineOfRightBlockElementIntoAncestorLeftBlockElement()`
1016 if (mPointContainingTheOtherBlockElement
.GetContainer() ==
1017 mLeftBlockElement
) {
1018 return mNewListElementTagNameOfRightListElement
.isSome() &&
1019 !mRightBlockElement
->GetChildCount();
1021 MOZ_ASSERT(!mPointContainingTheOtherBlockElement
.IsSet());
1022 // `MergeFirstLineOfRightBlockElementIntoLeftBlockElement()`
1023 return mNewListElementTagNameOfRightListElement
.isSome() ||
1024 mLeftBlockElement
->NodeInfo()->NameAtom() ==
1025 mRightBlockElement
->NodeInfo()->NameAtom();
1028 OwningNonNull
<nsIContent
> mInclusiveDescendantOfLeftBlockElement
;
1029 OwningNonNull
<nsIContent
> mInclusiveDescendantOfRightBlockElement
;
1030 RefPtr
<Element
> mLeftBlockElement
;
1031 RefPtr
<Element
> mRightBlockElement
;
1032 Maybe
<nsAtom
*> mNewListElementTagNameOfRightListElement
;
1033 EditorDOMPoint mPointContainingTheOtherBlockElement
;
1034 RefPtr
<dom::HTMLBRElement
> mPrecedingInvisibleBRElement
;
1035 bool mCanJoinBlocks
;
1036 bool mFallbackToDeleteLeafContent
;
1037 }; // HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
1038 // AutoInclusiveAncestorBlockElementsJoiner
1044 JoinBlocksInSameParent
,
1046 // The instance will handle only the <br> element immediately before a
1048 DeletePrecedingBRElementOfBlock
,
1049 // The instance will handle only the preceding preformatted line break
1051 DeletePrecedingPreformattedLineBreak
,
1052 DeleteContentInRange
,
1053 DeleteNonCollapsedRange
,
1054 // The instance will handle preceding lines of the right block and content
1055 // in the range in the right block.
1056 DeletePrecedingLinesAndContentInRange
,
1058 AutoDeleteRangesHandler
* mDeleteRangesHandler
;
1059 const AutoDeleteRangesHandler
& mDeleteRangesHandlerConst
;
1060 nsCOMPtr
<nsIContent
> mLeftContent
;
1061 nsCOMPtr
<nsIContent
> mRightContent
;
1062 nsCOMPtr
<nsIContent
> mLeafContentInOtherBlock
;
1063 // mSkippedInvisibleContents stores all content nodes which are skipped at
1064 // scanning mLeftContent and mRightContent. The content nodes should be
1065 // removed at deletion.
1066 AutoTArray
<OwningNonNull
<nsIContent
>, 8> mSkippedInvisibleContents
;
1067 RefPtr
<dom::HTMLBRElement
> mBRElement
;
1068 EditorDOMPointInText mPreformattedLineBreak
;
1069 Mode mMode
= Mode::NotInitialized
;
1070 }; // HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner
1072 class MOZ_STACK_CLASS AutoEmptyBlockAncestorDeleter final
{
1075 * ScanEmptyBlockInclusiveAncestor() scans an inclusive ancestor element
1076 * which is empty and a block element. Then, stores the result and
1077 * returns the found empty block element.
1079 * @param aHTMLEditor The HTMLEditor.
1080 * @param aStartContent Start content to look for empty ancestors.
1082 [[nodiscard
]] Element
* ScanEmptyBlockInclusiveAncestor(
1083 const HTMLEditor
& aHTMLEditor
, nsIContent
& aStartContent
);
1086 * ComputeTargetRanges() computes "target ranges" for deleting
1087 * `mEmptyInclusiveAncestorBlockElement`.
1089 nsresult
ComputeTargetRanges(
1090 const HTMLEditor
& aHTMLEditor
,
1091 nsIEditor::EDirection aDirectionAndAmount
, const Element
& aEditingHost
,
1092 AutoClonedSelectionRangeArray
& aRangesToDelete
) const;
1095 * Deletes found empty block element by `ScanEmptyBlockInclusiveAncestor()`.
1096 * If found one is a list item element, calls
1097 * `MaybeInsertBRElementBeforeEmptyListItemElement()` before deleting
1098 * the list item element.
1099 * If found empty ancestor is not a list item element,
1100 * `GetNewCaretPosition()` will be called to determine new caret position.
1101 * Finally, removes the empty block ancestor.
1103 * @param aHTMLEditor The HTMLEditor.
1104 * @param aDirectionAndAmount If found empty ancestor block is a list item
1105 * element, this is ignored. Otherwise:
1106 * - If eNext, eNextWord or eToEndOfLine,
1107 * collapse Selection to after found empty
1109 * - If ePrevious, ePreviousWord or
1110 * eToBeginningOfLine, collapse Selection to
1111 * end of previous editable node.
1112 * - Otherwise, eNone is allowed but does
1114 * @param aEditingHost The editing host.
1116 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT Result
<DeleteRangeResult
, nsresult
> Run(
1117 HTMLEditor
& aHTMLEditor
, nsIEditor::EDirection aDirectionAndAmount
,
1118 const Element
& aEditingHost
);
1122 * MaybeReplaceSubListWithNewListItem() replaces
1123 * mEmptyInclusiveAncestorBlockElement with new list item element
1124 * (containing <br>) if:
1125 * - mEmptyInclusiveAncestorBlockElement is a list element
1126 * - The parent of mEmptyInclusiveAncestorBlockElement is a list element
1127 * - The parent becomes empty after deletion
1128 * If this does not perform the replacement, returns "ignored".
1130 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT Result
<DeleteRangeResult
, nsresult
>
1131 MaybeReplaceSubListWithNewListItem(HTMLEditor
& aHTMLEditor
);
1134 * MaybeInsertBRElementBeforeEmptyListItemElement() inserts a `<br>` element
1135 * if `mEmptyInclusiveAncestorBlockElement` is a list item element which
1136 * is first editable element in its parent, and its grand parent is not a
1137 * list element, inserts a `<br>` element before the empty list item.
1139 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT Result
<CreateLineBreakResult
, nsresult
>
1140 MaybeInsertBRElementBeforeEmptyListItemElement(HTMLEditor
& aHTMLEditor
);
1143 * GetNewCaretPosition() returns new caret position after deleting
1144 * `mEmptyInclusiveAncestorBlockElement`.
1146 [[nodiscard
]] Result
<CaretPoint
, nsresult
> GetNewCaretPosition(
1147 const HTMLEditor
& aHTMLEditor
,
1148 nsIEditor::EDirection aDirectionAndAmount
) const;
1150 RefPtr
<Element
> mEmptyInclusiveAncestorBlockElement
;
1151 }; // HTMLEditor::AutoDeleteRangesHandler::AutoEmptyBlockAncestorDeleter
1153 const AutoDeleteRangesHandler
* const mParent
;
1154 nsIEditor::EDirection mOriginalDirectionAndAmount
;
1155 nsIEditor::EStripWrappers mOriginalStripWrappers
;
1156 }; // HTMLEditor::AutoDeleteRangesHandler
1158 nsresult
HTMLEditor::ComputeTargetRanges(
1159 nsIEditor::EDirection aDirectionAndAmount
,
1160 AutoClonedSelectionRangeArray
& aRangesToDelete
) const {
1161 MOZ_ASSERT(IsEditActionDataAvailable());
1163 Element
* editingHost
= ComputeEditingHost();
1165 aRangesToDelete
.RemoveAllRanges();
1166 return NS_ERROR_EDITOR_NO_EDITABLE_RANGE
;
1169 // First check for table selection mode. If so, hand off to table editor.
1170 SelectedTableCellScanner
scanner(aRangesToDelete
);
1171 if (scanner
.IsInTableCellSelectionMode()) {
1172 // If it's in table cell selection mode, we'll delete all childen in
1173 // the all selected table cell elements,
1174 if (scanner
.ElementsRef().Length() == aRangesToDelete
.Ranges().Length()) {
1177 // but will ignore all ranges which does not select a table cell.
1178 size_t removedRanges
= 0;
1179 for (size_t i
= 1; i
< scanner
.ElementsRef().Length(); i
++) {
1180 if (HTMLEditUtils::GetTableCellElementIfOnlyOneSelected(
1181 aRangesToDelete
.Ranges()[i
- removedRanges
]) !=
1182 scanner
.ElementsRef()[i
]) {
1183 // XXX Need to manage anchor-focus range too!
1184 aRangesToDelete
.Ranges().RemoveElementAt(i
- removedRanges
);
1191 aRangesToDelete
.EnsureOnlyEditableRanges(*editingHost
);
1192 if (aRangesToDelete
.Ranges().IsEmpty()) {
1194 "There is no range which we can delete entire of or around the caret");
1195 return NS_ERROR_EDITOR_NO_EDITABLE_RANGE
;
1197 AutoDeleteRangesHandler deleteHandler
;
1198 // Should we delete target ranges which cannot delete actually?
1199 nsresult rv
= deleteHandler
.ComputeRangesToDelete(
1200 *this, aDirectionAndAmount
, aRangesToDelete
, *editingHost
);
1201 NS_WARNING_ASSERTION(
1203 "AutoDeleteRangesHandler::ComputeRangesToDelete() failed");
1207 Result
<EditActionResult
, nsresult
> HTMLEditor::HandleDeleteSelection(
1208 nsIEditor::EDirection aDirectionAndAmount
,
1209 nsIEditor::EStripWrappers aStripWrappers
) {
1210 MOZ_ASSERT(IsEditActionDataAvailable());
1211 MOZ_ASSERT(aStripWrappers
== nsIEditor::eStrip
||
1212 aStripWrappers
== nsIEditor::eNoStrip
);
1214 if (MOZ_UNLIKELY(!SelectionRef().RangeCount())) {
1215 return Err(NS_ERROR_EDITOR_NO_EDITABLE_RANGE
);
1218 const RefPtr
<Element
> editingHost
= ComputeEditingHost();
1219 if (MOZ_UNLIKELY(!editingHost
)) {
1220 return Err(NS_ERROR_EDITOR_NO_EDITABLE_RANGE
);
1223 // Remember that we did a selection deletion. Used by
1224 // CreateStyleForInsertText()
1225 TopLevelEditSubActionDataRef().mDidDeleteSelection
= true;
1227 if (MOZ_UNLIKELY(IsEmpty())) {
1228 return EditActionResult::CanceledResult();
1231 // First check for table selection mode. If so, hand off to table editor.
1232 if (HTMLEditUtils::IsInTableCellSelectionMode(SelectionRef())) {
1233 nsresult rv
= DeleteTableCellContentsWithTransaction();
1234 if (NS_WARN_IF(Destroyed())) {
1235 return Err(NS_ERROR_EDITOR_DESTROYED
);
1237 if (NS_FAILED(rv
)) {
1238 NS_WARNING("HTMLEditor::DeleteTableCellContentsWithTransaction() failed");
1241 return EditActionResult::HandledResult();
1244 AutoClonedSelectionRangeArray
rangesToDelete(SelectionRef());
1245 rangesToDelete
.EnsureOnlyEditableRanges(*editingHost
);
1246 // AutoClonedSelectionRangeArray::ExtendAnchorFocusRangeFor() need to use
1247 // NodeIsInLimiters() to extend the range for deletion. But if focus event
1248 // doesn't receive yet, ancestor hasn't been set yet. So we need to set
1249 // ancestor limiter to editing host, <body> or something else in such case.
1250 if (!rangesToDelete
.GetAncestorLimiter()) {
1251 rangesToDelete
.SetAncestorLimiter(FindSelectionRoot(*editingHost
));
1253 if (MOZ_UNLIKELY(rangesToDelete
.Ranges().IsEmpty())) {
1255 "There is no range which we can delete entire the ranges or around the "
1257 return Err(NS_ERROR_EDITOR_NO_EDITABLE_RANGE
);
1259 AutoDeleteRangesHandler deleteHandler
;
1260 Result
<EditActionResult
, nsresult
> result
= deleteHandler
.Run(
1261 *this, aDirectionAndAmount
, aStripWrappers
, rangesToDelete
, *editingHost
);
1262 if (MOZ_UNLIKELY(result
.isErr()) || result
.inspect().Canceled()) {
1263 NS_WARNING_ASSERTION(result
.isOk(),
1264 "AutoDeleteRangesHandler::Run() failed");
1267 return EditActionResult::HandledResult();
1270 Result
<EditorDOMPoint
, nsresult
> HTMLEditor::DeleteLineBreakWithTransaction(
1271 const EditorLineBreak
& aLineBreak
,
1272 nsIEditor::EStripWrappers aDeleteEmptyInlines
,
1273 const Element
& aEditingHost
) {
1274 MOZ_ASSERT(aLineBreak
.IsInComposedDoc());
1275 MOZ_ASSERT_IF(aLineBreak
.IsPreformattedLineBreak(),
1276 aLineBreak
.CharAtOffsetIsLineBreak());
1278 if (aLineBreak
.IsHTMLBRElement() ||
1279 aLineBreak
.TextIsOnlyPreformattedLineBreak()) {
1280 const OwningNonNull
<nsIContent
> nodeToDelete
= [&]() -> nsIContent
& {
1281 if (aDeleteEmptyInlines
== nsIEditor::eNoStrip
) {
1282 return aLineBreak
.ContentRef();
1284 Element
* const newEmptyInlineElement
=
1285 HTMLEditUtils::GetMostDistantAncestorEditableEmptyInlineElement(
1286 aLineBreak
.ContentRef(),
1287 BlockInlineCheck::UseComputedDisplayOutsideStyle
, &aEditingHost
);
1288 return newEmptyInlineElement
? *newEmptyInlineElement
1289 : aLineBreak
.ContentRef();
1291 const nsCOMPtr
<nsINode
> parentNode
= nodeToDelete
->GetParentNode();
1292 if (NS_WARN_IF(!parentNode
)) {
1293 return Err(NS_ERROR_FAILURE
);
1295 const nsCOMPtr
<nsIContent
> nextSibling
= nodeToDelete
->GetNextSibling();
1296 nsresult rv
= DeleteNodeWithTransaction(nodeToDelete
);
1297 if (NS_FAILED(rv
)) {
1298 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
1301 if (NS_WARN_IF(nextSibling
&& nextSibling
->GetParentNode() != parentNode
) ||
1302 NS_WARN_IF(!parentNode
->IsInComposedDoc())) {
1303 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
);
1305 return nextSibling
? EditorDOMPoint(nextSibling
)
1306 : EditorDOMPoint::AtEndOf(*parentNode
);
1309 const OwningNonNull
<Text
> textNode(aLineBreak
.TextRef());
1310 Result
<CaretPoint
, nsresult
> caretPointOrError
=
1311 DeleteTextWithTransaction(textNode
, aLineBreak
.Offset(), 1u);
1312 if (MOZ_UNLIKELY(caretPointOrError
.isErr())) {
1313 NS_WARNING("HTMLEditor::DeleteTextWithTransaction() failed");
1314 return caretPointOrError
.propagateErr();
1316 if (NS_WARN_IF(!caretPointOrError
.inspect().HasCaretPointSuggestion())) {
1317 return Err(NS_ERROR_FAILURE
);
1319 return caretPointOrError
.unwrap().UnwrapCaretPoint();
1322 Result
<CaretPoint
, nsresult
> HTMLEditor::DeleteRangesWithTransaction(
1323 nsIEditor::EDirection aDirectionAndAmount
,
1324 nsIEditor::EStripWrappers aStripWrappers
,
1325 const AutoClonedRangeArray
& aRangesToDelete
) {
1326 const RefPtr
<Element
> editingHost
=
1327 ComputeEditingHost(LimitInBodyElement::No
);
1328 if (NS_WARN_IF(!editingHost
)) {
1329 return Err(NS_ERROR_UNEXPECTED
);
1332 Result
<CaretPoint
, nsresult
> result
= EditorBase::DeleteRangesWithTransaction(
1333 aDirectionAndAmount
, aStripWrappers
, aRangesToDelete
);
1334 if (MOZ_UNLIKELY(result
.isErr())) {
1338 const bool isDeleteSelection
=
1339 GetTopLevelEditSubAction() == EditSubAction::eDeleteSelectedContent
;
1340 EditorDOMPoint pointToPutCaret
= result
.unwrap().UnwrapCaretPoint();
1342 AutoTrackDOMPoint
trackCaretPoint(RangeUpdaterRef(), &pointToPutCaret
);
1343 for (const auto& range
: aRangesToDelete
.Ranges()) {
1344 // Refer the start boundary of the range because it should be end of the
1345 // preceding content, but the end boundary may be in an ancestor when an
1346 // ancestor element of end boundary has already been deleted.
1347 if (MOZ_UNLIKELY(!range
->IsPositioned() ||
1348 !range
->GetStartContainer()->IsContent())) {
1351 EditorDOMPoint
pointToInsertLineBreak(range
->StartRef());
1352 // Don't remove empty inline elements in the plaintext-only mode because
1353 // nobody can restore the style again.
1354 if (aStripWrappers
== nsIEditor::eStrip
&&
1355 !editingHost
->IsContentEditablePlainTextOnly()) {
1356 const OwningNonNull
<nsIContent
> maybeEmptyContent
=
1357 *pointToInsertLineBreak
.ContainerAs
<nsIContent
>();
1358 Result
<CaretPoint
, nsresult
> caretPointOrError
=
1359 DeleteEmptyInclusiveAncestorInlineElements(maybeEmptyContent
,
1361 if (MOZ_UNLIKELY(caretPointOrError
.isErr())) {
1363 "HTMLEditor::DeleteEmptyInclusiveAncestorInlineElements() "
1365 return caretPointOrError
.propagateErr();
1367 if (NS_WARN_IF(!range
->IsPositioned() ||
1368 !range
->GetStartContainer()->IsContent())) {
1371 caretPointOrError
.unwrap().MoveCaretPointTo(
1372 pointToInsertLineBreak
, {SuggestCaret::OnlyIfHasSuggestion
});
1373 if (NS_WARN_IF(!pointToInsertLineBreak
.IsSetAndValidInComposedDoc())) {
1378 if ((IsMailEditor() || IsPlaintextMailComposer()) &&
1379 MOZ_LIKELY(pointToInsertLineBreak
.IsInContentNode())) {
1380 AutoTrackDOMPoint
trackPointToInsertLineBreak(RangeUpdaterRef(),
1381 &pointToInsertLineBreak
);
1382 nsresult rv
= DeleteMostAncestorMailCiteElementIfEmpty(
1383 MOZ_KnownLive(*pointToInsertLineBreak
.ContainerAs
<nsIContent
>()));
1384 if (NS_FAILED(rv
)) {
1386 "HTMLEditor::DeleteMostAncestorMailCiteElementIfEmpty() failed");
1389 trackPointToInsertLineBreak
.FlushAndStopTracking();
1390 if (NS_WARN_IF(!pointToInsertLineBreak
.IsSetAndValidInComposedDoc())) {
1395 if (isDeleteSelection
) {
1396 Result
<CreateLineBreakResult
, nsresult
> insertPaddingBRElementOrError
=
1397 InsertPaddingBRElementIfNeeded(
1398 pointToInsertLineBreak
,
1399 editingHost
->IsContentEditablePlainTextOnly()
1400 ? nsIEditor::eNoStrip
1401 : nsIEditor::eStrip
,
1403 if (MOZ_UNLIKELY(insertPaddingBRElementOrError
.isErr())) {
1404 NS_WARNING("HTMLEditor::InsertPaddingBRElementIfNeeded() failed");
1405 return insertPaddingBRElementOrError
.propagateErr();
1407 insertPaddingBRElementOrError
.unwrap().IgnoreCaretPointSuggestion();
1411 return CaretPoint(std::move(pointToPutCaret
));
1414 nsresult
HTMLEditor::AutoDeleteRangesHandler::ComputeRangesToDelete(
1415 const HTMLEditor
& aHTMLEditor
, nsIEditor::EDirection aDirectionAndAmount
,
1416 AutoClonedSelectionRangeArray
& aRangesToDelete
,
1417 const Element
& aEditingHost
) {
1418 MOZ_ASSERT(aHTMLEditor
.IsEditActionDataAvailable());
1419 MOZ_ASSERT(!aRangesToDelete
.Ranges().IsEmpty());
1421 mOriginalDirectionAndAmount
= aDirectionAndAmount
;
1422 mOriginalStripWrappers
= nsIEditor::eNoStrip
;
1424 if (aHTMLEditor
.mPaddingBRElementForEmptyEditor
) {
1425 nsresult rv
= aRangesToDelete
.Collapse(
1426 EditorRawDOMPoint(aHTMLEditor
.mPaddingBRElementForEmptyEditor
));
1427 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
1428 "AutoClonedRangeArray::Collapse() failed");
1432 SelectionWasCollapsed selectionWasCollapsed
= aRangesToDelete
.IsCollapsed()
1433 ? SelectionWasCollapsed::Yes
1434 : SelectionWasCollapsed::No
;
1435 if (selectionWasCollapsed
== SelectionWasCollapsed::Yes
) {
1436 const auto startPoint
=
1437 aRangesToDelete
.GetFirstRangeStartPoint
<EditorDOMPoint
>();
1438 if (NS_WARN_IF(!startPoint
.IsSet())) {
1439 return NS_ERROR_FAILURE
;
1441 if (startPoint
.IsInContentNode()) {
1442 AutoEmptyBlockAncestorDeleter deleter
;
1443 if (deleter
.ScanEmptyBlockInclusiveAncestor(
1444 aHTMLEditor
, *startPoint
.ContainerAs
<nsIContent
>())) {
1445 nsresult rv
= deleter
.ComputeTargetRanges(
1446 aHTMLEditor
, aDirectionAndAmount
, aEditingHost
, aRangesToDelete
);
1447 NS_WARNING_ASSERTION(
1449 "AutoEmptyBlockAncestorDeleter::ComputeTargetRanges() failed");
1454 // We shouldn't update caret bidi level right now, but we need to check
1455 // whether the deletion will be canceled or not.
1456 AutoCaretBidiLevelManager
bidiLevelManager(aHTMLEditor
, aDirectionAndAmount
,
1458 if (bidiLevelManager
.Failed()) {
1460 "EditorBase::AutoCaretBidiLevelManager failed to initialize itself");
1461 return NS_ERROR_FAILURE
;
1463 if (bidiLevelManager
.Canceled()) {
1464 return NS_SUCCESS_DOM_NO_OPERATION
;
1467 Result
<nsIEditor::EDirection
, nsresult
> extendResult
=
1468 aRangesToDelete
.ExtendAnchorFocusRangeFor(aHTMLEditor
,
1469 aDirectionAndAmount
);
1470 if (extendResult
.isErr()) {
1472 "AutoClonedSelectionRangeArray::ExtendAnchorFocusRangeFor() failed");
1473 return extendResult
.unwrapErr();
1476 // For compatibility with other browsers, we should set target ranges
1477 // to start from and/or end after an atomic content rather than start
1478 // from preceding text node end nor end at following text node start.
1479 Result
<bool, nsresult
> shrunkenResult
=
1480 aRangesToDelete
.ShrinkRangesIfStartFromOrEndAfterAtomicContent(
1481 aHTMLEditor
, aDirectionAndAmount
,
1482 AutoClonedRangeArray::IfSelectingOnlyOneAtomicContent::Collapse
);
1483 if (shrunkenResult
.isErr()) {
1485 "AutoClonedRangeArray::"
1486 "ShrinkRangesIfStartFromOrEndAfterAtomicContent() "
1488 return shrunkenResult
.unwrapErr();
1491 if (!shrunkenResult
.inspect() || !aRangesToDelete
.IsCollapsed()) {
1492 aDirectionAndAmount
= extendResult
.unwrap();
1495 if (aDirectionAndAmount
== nsIEditor::eNone
) {
1496 MOZ_ASSERT(aRangesToDelete
.Ranges().Length() == 1);
1497 if (!CanFallbackToDeleteRangesWithTransaction(aRangesToDelete
)) {
1498 // XXX In this case, do we need to modify the range again?
1499 return NS_SUCCESS_DOM_NO_OPERATION
;
1501 nsresult rv
= FallbackToComputeRangesToDeleteRangesWithTransaction(
1502 aHTMLEditor
, aRangesToDelete
, aEditingHost
);
1503 NS_WARNING_ASSERTION(
1505 "AutoDeleteRangesHandler::"
1506 "FallbackToComputeRangesToDeleteRangesWithTransaction() failed");
1510 if (aRangesToDelete
.IsCollapsed()) {
1511 const auto caretPoint
=
1512 aRangesToDelete
.GetFirstRangeStartPoint
<EditorDOMPoint
>();
1513 if (MOZ_UNLIKELY(NS_WARN_IF(!caretPoint
.IsInContentNode()))) {
1514 return NS_ERROR_FAILURE
;
1516 if (!EditorUtils::IsEditableContent(*caretPoint
.ContainerAs
<nsIContent
>(),
1517 EditorType::HTML
)) {
1518 return NS_SUCCESS_DOM_NO_OPERATION
;
1520 WSRunScanner
wsRunScannerAtCaret(
1521 &aEditingHost
, caretPoint
,
1522 BlockInlineCheck::UseComputedDisplayOutsideStyle
);
1523 const WSScanResult scanFromCaretPointResult
=
1524 aDirectionAndAmount
== nsIEditor::eNext
1525 ? wsRunScannerAtCaret
1526 .ScanInclusiveNextVisibleNodeOrBlockBoundaryFrom(caretPoint
)
1527 : wsRunScannerAtCaret
.ScanPreviousVisibleNodeOrBlockBoundaryFrom(
1529 if (scanFromCaretPointResult
.Failed()) {
1531 "WSRunScanner::Scan(Next|Previous)VisibleNodeOrBlockBoundaryFrom() "
1533 return NS_ERROR_FAILURE
;
1535 MOZ_ASSERT(scanFromCaretPointResult
.GetContent());
1537 if (scanFromCaretPointResult
.ReachedBRElement()) {
1538 if (scanFromCaretPointResult
.BRElementPtr() ==
1539 wsRunScannerAtCaret
.GetEditingHost()) {
1542 if (!scanFromCaretPointResult
.IsContentEditable()) {
1543 return NS_SUCCESS_DOM_NO_OPERATION
;
1545 if (scanFromCaretPointResult
.ReachedInvisibleBRElement()) {
1546 EditorDOMPoint newCaretPosition
=
1547 aDirectionAndAmount
== nsIEditor::eNext
1548 ? scanFromCaretPointResult
1549 .PointAfterReachedContent
<EditorDOMPoint
>()
1550 : scanFromCaretPointResult
1551 .PointAtReachedContent
<EditorDOMPoint
>();
1552 if (NS_WARN_IF(!newCaretPosition
.IsSet())) {
1553 return NS_ERROR_FAILURE
;
1555 AutoHideSelectionChanges
blockSelectionListeners(
1556 aHTMLEditor
.SelectionRef());
1557 nsresult rv
= aHTMLEditor
.CollapseSelectionTo(newCaretPosition
);
1558 if (MOZ_UNLIKELY(NS_FAILED(rv
))) {
1559 NS_WARNING("EditorBase::CollapseSelectionTo() failed");
1560 return NS_ERROR_FAILURE
;
1562 if (NS_WARN_IF(!aHTMLEditor
.SelectionRef().RangeCount())) {
1563 return NS_ERROR_UNEXPECTED
;
1565 aRangesToDelete
.Initialize(aHTMLEditor
.SelectionRef());
1566 AutoDeleteRangesHandler
anotherHandler(this);
1567 rv
= anotherHandler
.ComputeRangesToDelete(
1568 aHTMLEditor
, aDirectionAndAmount
, aRangesToDelete
, aEditingHost
);
1569 NS_WARNING_ASSERTION(
1571 "Recursive AutoDeleteRangesHandler::ComputeRangesToDelete() "
1574 rv
= aHTMLEditor
.CollapseSelectionTo(caretPoint
);
1575 if (MOZ_UNLIKELY(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
1577 "EditorBase::CollapseSelectionTo() caused destroying the "
1579 return NS_ERROR_EDITOR_DESTROYED
;
1581 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
1582 "EditorBase::CollapseSelectionTo() failed to "
1583 "restore original selection, but ignored");
1585 MOZ_ASSERT(aRangesToDelete
.Ranges().Length() == 1);
1586 // If the range is collapsed, there is no content which should
1587 // be removed together. In this case, only the invisible `<br>`
1588 // element should be selected.
1589 if (aRangesToDelete
.IsCollapsed()) {
1590 nsresult rv
= aRangesToDelete
.SelectNode(
1591 *scanFromCaretPointResult
.BRElementPtr());
1592 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
1593 "AutoClonedRangeArray::SelectNode() failed");
1597 // Otherwise, extend the range to contain the invisible `<br>`
1599 if (scanFromCaretPointResult
1600 .PointAtReachedContent
<EditorRawDOMPoint
>()
1603 .GetFirstRangeStartPoint
<EditorRawDOMPoint
>())) {
1604 nsresult rv
= aRangesToDelete
.FirstRangeRef()->SetStartAndEnd(
1605 EditorRawDOMPoint(scanFromCaretPointResult
.BRElementPtr())
1606 .ToRawRangeBoundary(),
1607 aRangesToDelete
.FirstRangeRef()->EndRef());
1608 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
1609 "nsRange::SetStartAndEnd() failed");
1612 if (aRangesToDelete
.GetFirstRangeEndPoint
<EditorRawDOMPoint
>()
1614 scanFromCaretPointResult
1615 .PointAfterReachedContent
<EditorRawDOMPoint
>())) {
1616 nsresult rv
= aRangesToDelete
.FirstRangeRef()->SetStartAndEnd(
1617 aRangesToDelete
.FirstRangeRef()->StartRef(),
1618 scanFromCaretPointResult
1619 .PointAfterReachedContent
<EditorRawDOMPoint
>()
1620 .ToRawRangeBoundary());
1621 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
1622 "nsRange::SetStartAndEnd() failed");
1625 NS_WARNING("Was the invisible `<br>` element selected?");
1630 nsresult rv
= ComputeRangesToDeleteAroundCollapsedRanges(
1631 aHTMLEditor
, aDirectionAndAmount
, aRangesToDelete
,
1632 wsRunScannerAtCaret
, scanFromCaretPointResult
, aEditingHost
);
1633 NS_WARNING_ASSERTION(
1635 "AutoDeleteRangesHandler::ComputeRangesToDeleteAroundCollapsedRanges("
1641 nsresult rv
= ComputeRangesToDeleteNonCollapsedRanges(
1642 aHTMLEditor
, aDirectionAndAmount
, aRangesToDelete
, selectionWasCollapsed
,
1644 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
1645 "AutoDeleteRangesHandler::"
1646 "ComputeRangesToDeleteNonCollapsedRanges() failed");
1650 Result
<EditActionResult
, nsresult
> HTMLEditor::AutoDeleteRangesHandler::Run(
1651 HTMLEditor
& aHTMLEditor
, nsIEditor::EDirection aDirectionAndAmount
,
1652 nsIEditor::EStripWrappers aStripWrappers
,
1653 AutoClonedSelectionRangeArray
& aRangesToDelete
,
1654 const Element
& aEditingHost
) {
1655 MOZ_ASSERT(aHTMLEditor
.IsEditActionDataAvailable());
1656 MOZ_ASSERT(aStripWrappers
== nsIEditor::eStrip
||
1657 aStripWrappers
== nsIEditor::eNoStrip
);
1658 MOZ_ASSERT(!aRangesToDelete
.Ranges().IsEmpty());
1660 mOriginalDirectionAndAmount
= aDirectionAndAmount
;
1661 mOriginalStripWrappers
= aStripWrappers
;
1663 if (MOZ_UNLIKELY(aHTMLEditor
.IsEmpty())) {
1664 return EditActionResult::CanceledResult();
1667 // selectionWasCollapsed is used later to determine whether we should join
1668 // blocks in HandleDeleteNonCollapsedRanges(). We don't really care about
1669 // collapsed because it will be modified by
1670 // AutoClonedSelectionRangeArray::ExtendAnchorFocusRangeFor() later.
1671 // AutoBlockElementsJoiner::AutoInclusiveAncestorBlockElementsJoiner should
1672 // happen if the original selection is collapsed and the cursor is at the end
1673 // of a block element, in which case
1674 // AutoClonedSelectionRangeArray::ExtendAnchorFocusRangeFor() would always
1675 // make the selection not collapsed.
1676 SelectionWasCollapsed selectionWasCollapsed
= aRangesToDelete
.IsCollapsed()
1677 ? SelectionWasCollapsed::Yes
1678 : SelectionWasCollapsed::No
;
1680 if (selectionWasCollapsed
== SelectionWasCollapsed::Yes
) {
1681 const auto startPoint
=
1682 aRangesToDelete
.GetFirstRangeStartPoint
<EditorDOMPoint
>();
1683 if (NS_WARN_IF(!startPoint
.IsSet())) {
1684 return Err(NS_ERROR_FAILURE
);
1687 // If we are inside an empty block, delete it.
1688 if (startPoint
.IsInContentNode()) {
1690 nsMutationGuard debugMutation
;
1691 #endif // #ifdef DEBUG
1692 AutoEmptyBlockAncestorDeleter deleter
;
1693 if (deleter
.ScanEmptyBlockInclusiveAncestor(
1694 aHTMLEditor
, *startPoint
.ContainerAs
<nsIContent
>())) {
1695 Result
<DeleteRangeResult
, nsresult
> deleteResultOrError
=
1696 deleter
.Run(aHTMLEditor
, aDirectionAndAmount
, aEditingHost
);
1697 if (MOZ_UNLIKELY(deleteResultOrError
.isErr())) {
1698 NS_WARNING("AutoEmptyBlockAncestorDeleter::Run() failed");
1699 return deleteResultOrError
.propagateErr();
1701 DeleteRangeResult deleteResult
= deleteResultOrError
.unwrap();
1702 if (deleteResult
.Handled()) {
1703 nsresult rv
= deleteResult
.SuggestCaretPointTo(
1704 aHTMLEditor
, {SuggestCaret::OnlyIfHasSuggestion
});
1705 if (NS_FAILED(rv
)) {
1706 NS_WARNING("CaretPoint::SuggestCaretPoint() failed");
1709 return EditActionResult::HandledResult();
1712 MOZ_ASSERT(!debugMutation
.Mutated(0),
1713 "AutoEmptyBlockAncestorDeleter shouldn't modify the DOM tree "
1714 "if it returns not handled nor error");
1717 // Test for distance between caret and text that will be deleted.
1718 AutoCaretBidiLevelManager
bidiLevelManager(aHTMLEditor
, aDirectionAndAmount
,
1720 if (MOZ_UNLIKELY(bidiLevelManager
.Failed())) {
1722 "EditorBase::AutoCaretBidiLevelManager failed to initialize itself");
1723 return Err(NS_ERROR_FAILURE
);
1725 bidiLevelManager
.MaybeUpdateCaretBidiLevel(aHTMLEditor
);
1726 if (bidiLevelManager
.Canceled()) {
1727 return EditActionResult::CanceledResult();
1730 // Calling `ExtendAnchorFocusRangeFor()` and
1731 // `ShrinkRangesIfStartFromOrEndAfterAtomicContent()` may move caret to
1732 // the container of deleting atomic content. However, it may be different
1733 // from the original caret's container. The original caret container may
1734 // be important to put caret after deletion so that let's cache the
1735 // original position.
1736 Maybe
<EditorDOMPoint
> caretPoint
;
1737 if (aRangesToDelete
.IsCollapsed() && !aRangesToDelete
.Ranges().IsEmpty()) {
1739 Some(aRangesToDelete
.GetFirstRangeStartPoint
<EditorDOMPoint
>());
1740 if (NS_WARN_IF(!caretPoint
.ref().IsInContentNode())) {
1741 return Err(NS_ERROR_FAILURE
);
1745 Result
<nsIEditor::EDirection
, nsresult
> extendResult
=
1746 aRangesToDelete
.ExtendAnchorFocusRangeFor(aHTMLEditor
,
1747 aDirectionAndAmount
);
1748 if (MOZ_UNLIKELY(extendResult
.isErr())) {
1750 "AutoClonedSelectionRangeArray::ExtendAnchorFocusRangeFor() failed");
1751 return extendResult
.propagateErr();
1753 if (caretPoint
.isSome() &&
1754 MOZ_UNLIKELY(!caretPoint
.ref().IsSetAndValid())) {
1755 NS_WARNING("The caret position became invalid");
1756 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
);
1759 // If there is only one range and it selects an atomic content, we should
1760 // delete it with collapsed range path for making consistent behavior
1761 // between both cases, the content is selected case and caret is at it or
1763 Result
<bool, nsresult
> shrunkenResult
=
1764 aRangesToDelete
.ShrinkRangesIfStartFromOrEndAfterAtomicContent(
1765 aHTMLEditor
, aDirectionAndAmount
,
1766 AutoClonedRangeArray::IfSelectingOnlyOneAtomicContent::Collapse
);
1767 if (MOZ_UNLIKELY(shrunkenResult
.isErr())) {
1769 "AutoClonedRangeArray::"
1770 "ShrinkRangesIfStartFromOrEndAfterAtomicContent() "
1772 return shrunkenResult
.propagateErr();
1775 if (!shrunkenResult
.inspect() || !aRangesToDelete
.IsCollapsed()) {
1776 aDirectionAndAmount
= extendResult
.unwrap();
1779 if (aDirectionAndAmount
== nsIEditor::eNone
) {
1780 MOZ_ASSERT(aRangesToDelete
.Ranges().Length() == 1);
1781 if (!CanFallbackToDeleteRangesWithTransaction(aRangesToDelete
)) {
1782 return EditActionResult::IgnoredResult();
1784 Result
<CaretPoint
, nsresult
> caretPointOrError
=
1785 FallbackToDeleteRangesWithTransaction(aHTMLEditor
, aRangesToDelete
);
1786 if (MOZ_UNLIKELY(caretPointOrError
.isErr())) {
1788 "AutoDeleteRangesHandler::FallbackToDeleteRangesWithTransaction() "
1791 nsresult rv
= caretPointOrError
.inspect().SuggestCaretPointTo(
1792 aHTMLEditor
, {SuggestCaret::OnlyIfHasSuggestion
,
1793 SuggestCaret::OnlyIfTransactionsAllowedToDoIt
,
1794 SuggestCaret::AndIgnoreTrivialError
});
1795 if (NS_FAILED(rv
)) {
1796 NS_WARNING("CaretPoint::SuggestCaretPointTo() failed");
1799 NS_WARNING_ASSERTION(
1800 rv
!= NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR
,
1801 "CaretPoint::SuggestCaretPointTo() failed, but ignored");
1802 // Don't return "ignored" to avoid to fall it back to delete ranges
1804 return EditActionResult::HandledResult();
1807 if (aRangesToDelete
.IsCollapsed()) {
1808 // Use the original caret position for handling the deletion around
1809 // collapsed range because the container may be different from the
1810 // new collapsed position's container.
1811 if (!EditorUtils::IsEditableContent(
1812 *caretPoint
.ref().ContainerAs
<nsIContent
>(), EditorType::HTML
)) {
1813 return EditActionResult::CanceledResult();
1815 WSRunScanner
wsRunScannerAtCaret(
1816 &aEditingHost
, caretPoint
.ref(),
1817 BlockInlineCheck::UseComputedDisplayOutsideStyle
);
1818 const WSScanResult scanFromCaretPointResult
=
1819 aDirectionAndAmount
== nsIEditor::eNext
1820 ? wsRunScannerAtCaret
1821 .ScanInclusiveNextVisibleNodeOrBlockBoundaryFrom(
1823 : wsRunScannerAtCaret
.ScanPreviousVisibleNodeOrBlockBoundaryFrom(
1825 if (MOZ_UNLIKELY(scanFromCaretPointResult
.Failed())) {
1827 "WSRunScanner::Scan(Next|Previous)VisibleNodeOrBlockBoundaryFrom() "
1829 return Err(NS_ERROR_FAILURE
);
1831 MOZ_ASSERT(scanFromCaretPointResult
.GetContent());
1833 // Short circuit for invisible breaks. delete them and recurse.
1834 if (scanFromCaretPointResult
.ReachedBRElement()) {
1835 if (scanFromCaretPointResult
.BRElementPtr() == &aEditingHost
) {
1836 return EditActionResult::HandledResult();
1838 if (!scanFromCaretPointResult
.IsContentEditable()) {
1839 return EditActionResult::CanceledResult();
1841 if (scanFromCaretPointResult
.ReachedInvisibleBRElement()) {
1842 // TODO: We should extend the range to delete again before/after
1843 // the caret point and use `HandleDeleteNonCollapsedRanges()`
1844 // instead after we would create delete range computation
1845 // method at switching to the new white-space normalizer.
1846 Result
<CaretPoint
, nsresult
> caretPointOrError
=
1847 WhiteSpaceVisibilityKeeper::
1848 DeleteContentNodeAndJoinTextNodesAroundIt(
1850 MOZ_KnownLive(*scanFromCaretPointResult
.BRElementPtr()),
1851 caretPoint
.ref(), aEditingHost
);
1852 if (MOZ_UNLIKELY(caretPointOrError
.isErr())) {
1854 "WhiteSpaceVisibilityKeeper::"
1855 "DeleteContentNodeAndJoinTextNodesAroundIt() failed");
1856 return caretPointOrError
.propagateErr();
1858 if (caretPointOrError
.inspect().HasCaretPointSuggestion()) {
1859 caretPoint
= Some(caretPointOrError
.unwrap().UnwrapCaretPoint());
1861 if (NS_WARN_IF(!caretPoint
->IsSetAndValid())) {
1862 return Err(NS_ERROR_FAILURE
);
1864 AutoClonedSelectionRangeArray
rangesToDelete(
1865 caretPoint
.ref(), aRangesToDelete
.LimitersAndCaretDataRef());
1866 if (NS_WARN_IF(rangesToDelete
.Ranges().IsEmpty())) {
1867 return Err(NS_ERROR_FAILURE
);
1869 if (aHTMLEditor
.MayHaveMutationEventListeners(
1870 NS_EVENT_BITS_MUTATION_SUBTREEMODIFIED
|
1871 NS_EVENT_BITS_MUTATION_NODEREMOVED
|
1872 NS_EVENT_BITS_MUTATION_NODEREMOVEDFROMDOCUMENT
)) {
1873 // Let's check whether there is new invisible `<br>` element
1874 // for avoiding infinite recursive calls.
1875 WSRunScanner
wsRunScannerAtCaret(
1876 &aEditingHost
, caretPoint
.ref(),
1877 BlockInlineCheck::UseComputedDisplayOutsideStyle
);
1878 const WSScanResult scanFromCaretPointResult
=
1879 aDirectionAndAmount
== nsIEditor::eNext
1880 ? wsRunScannerAtCaret
1881 .ScanInclusiveNextVisibleNodeOrBlockBoundaryFrom(
1883 : wsRunScannerAtCaret
1884 .ScanPreviousVisibleNodeOrBlockBoundaryFrom(
1886 if (MOZ_UNLIKELY(scanFromCaretPointResult
.Failed())) {
1888 "WSRunScanner::Scan(Next|Previous)"
1889 "VisibleNodeOrBlockBoundaryFrom() failed");
1890 return Err(NS_ERROR_FAILURE
);
1893 scanFromCaretPointResult
.ReachedInvisibleBRElement())) {
1894 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
);
1897 AutoDeleteRangesHandler
anotherHandler(this);
1898 Result
<EditActionResult
, nsresult
> result
=
1899 anotherHandler
.Run(aHTMLEditor
, aDirectionAndAmount
,
1900 aStripWrappers
, rangesToDelete
, aEditingHost
);
1901 NS_WARNING_ASSERTION(
1902 result
.isOk(), "Recursive AutoDeleteRangesHandler::Run() failed");
1907 Result
<EditActionResult
, nsresult
> result
=
1908 HandleDeleteAroundCollapsedRanges(
1909 aHTMLEditor
, aDirectionAndAmount
, aStripWrappers
, aRangesToDelete
,
1910 wsRunScannerAtCaret
, scanFromCaretPointResult
, aEditingHost
);
1911 NS_WARNING_ASSERTION(result
.isOk(),
1912 "AutoDeleteRangesHandler::"
1913 "HandleDeleteAroundCollapsedRanges() failed");
1918 Result
<EditActionResult
, nsresult
> result
= HandleDeleteNonCollapsedRanges(
1919 aHTMLEditor
, aDirectionAndAmount
, aStripWrappers
, aRangesToDelete
,
1920 selectionWasCollapsed
, aEditingHost
);
1921 NS_WARNING_ASSERTION(
1923 "AutoDeleteRangesHandler::HandleDeleteNonCollapsedRanges() failed");
1928 HTMLEditor::AutoDeleteRangesHandler::ComputeRangesToDeleteAroundCollapsedRanges(
1929 const HTMLEditor
& aHTMLEditor
, nsIEditor::EDirection aDirectionAndAmount
,
1930 AutoClonedSelectionRangeArray
& aRangesToDelete
,
1931 const WSRunScanner
& aWSRunScannerAtCaret
,
1932 const WSScanResult
& aScanFromCaretPointResult
,
1933 const Element
& aEditingHost
) const {
1934 if (aScanFromCaretPointResult
.InCollapsibleWhiteSpaces() ||
1935 aScanFromCaretPointResult
.InNonCollapsibleCharacters() ||
1936 aScanFromCaretPointResult
.ReachedPreformattedLineBreak()) {
1937 // This means that if aDirectionAndAmount == nsIEditor::eNext, collapse
1938 // selection at the found character. Otherwise, collapse selection after
1939 // the found character.
1940 nsresult rv
= aRangesToDelete
.Collapse(
1941 aScanFromCaretPointResult
.Point_Deprecated
<EditorRawDOMPoint
>());
1942 if (MOZ_UNLIKELY(NS_FAILED(rv
))) {
1943 NS_WARNING("AutoClonedRangeArray::Collapse() failed");
1944 return NS_ERROR_FAILURE
;
1946 rv
= ComputeRangesToDeleteTextAroundCollapsedRanges(aDirectionAndAmount
,
1948 NS_WARNING_ASSERTION(
1950 "AutoDeleteRangesHandler::"
1951 "ComputeRangesToDeleteTextAroundCollapsedRanges() failed");
1955 if (aScanFromCaretPointResult
.ReachedSpecialContent() ||
1956 aScanFromCaretPointResult
.ReachedBRElement() ||
1957 aScanFromCaretPointResult
.ReachedHRElement() ||
1958 aScanFromCaretPointResult
.ReachedNonEditableOtherBlockElement()) {
1959 if (aScanFromCaretPointResult
.GetContent() ==
1960 aWSRunScannerAtCaret
.GetEditingHost()) {
1963 nsIContent
* atomicContent
= GetAtomicContentToDelete(
1964 aDirectionAndAmount
, aWSRunScannerAtCaret
, aScanFromCaretPointResult
);
1965 if (!HTMLEditUtils::IsRemovableNode(*atomicContent
)) {
1967 "AutoDeleteRangesHandler::GetAtomicContentToDelete() cannot find "
1968 "removable atomic content");
1969 return NS_ERROR_FAILURE
;
1972 ComputeRangesToDeleteAtomicContent(*atomicContent
, aRangesToDelete
);
1973 NS_WARNING_ASSERTION(
1975 "AutoDeleteRangesHandler::ComputeRangesToDeleteAtomicContent() failed");
1979 if (aScanFromCaretPointResult
.ReachedOtherBlockElement()) {
1980 if (NS_WARN_IF(!aScanFromCaretPointResult
.ContentIsElement())) {
1981 return NS_ERROR_FAILURE
;
1983 MOZ_ASSERT(!aRangesToDelete
.Ranges().IsEmpty());
1984 bool handled
= false;
1985 for (const OwningNonNull
<nsRange
>& range
: aRangesToDelete
.Ranges()) {
1986 MOZ_ASSERT(range
->IsPositioned());
1987 AutoBlockElementsJoiner
joiner(*this);
1988 if (!joiner
.PrepareToDeleteAtOtherBlockBoundary(
1989 aHTMLEditor
, aDirectionAndAmount
,
1990 *aScanFromCaretPointResult
.ElementPtr(),
1991 aWSRunScannerAtCaret
.ScanStartRef(), aWSRunScannerAtCaret
)) {
1995 nsresult rv
= joiner
.ComputeRangeToDelete(
1996 aHTMLEditor
, aDirectionAndAmount
, aWSRunScannerAtCaret
.ScanStartRef(),
1997 range
, aEditingHost
);
1998 if (NS_FAILED(rv
)) {
2000 "AutoBlockElementsJoiner::ComputeRangeToDelete() failed (other "
2005 return handled
? NS_OK
: NS_SUCCESS_DOM_NO_OPERATION
;
2008 if (aScanFromCaretPointResult
.ReachedCurrentBlockBoundary() ||
2009 aScanFromCaretPointResult
.ReachedInlineEditingHostBoundary()) {
2010 MOZ_ASSERT(aScanFromCaretPointResult
.ContentIsElement());
2011 MOZ_ASSERT(!aRangesToDelete
.Ranges().IsEmpty());
2012 bool handled
= false;
2013 for (const OwningNonNull
<nsRange
>& range
: aRangesToDelete
.Ranges()) {
2014 AutoBlockElementsJoiner
joiner(*this);
2015 if (!joiner
.PrepareToDeleteAtCurrentBlockBoundary(
2016 aHTMLEditor
, aDirectionAndAmount
,
2017 *aScanFromCaretPointResult
.ElementPtr(),
2018 aWSRunScannerAtCaret
.ScanStartRef(), aEditingHost
)) {
2022 nsresult rv
= joiner
.ComputeRangeToDelete(
2023 aHTMLEditor
, aDirectionAndAmount
, aWSRunScannerAtCaret
.ScanStartRef(),
2024 range
, aEditingHost
);
2025 if (NS_FAILED(rv
)) {
2027 "AutoBlockElementsJoiner::ComputeRangeToDelete() failed (current "
2032 return handled
? NS_OK
: NS_SUCCESS_DOM_NO_OPERATION
;
2038 Result
<EditActionResult
, nsresult
>
2039 HTMLEditor::AutoDeleteRangesHandler::HandleDeleteAroundCollapsedRanges(
2040 HTMLEditor
& aHTMLEditor
, nsIEditor::EDirection aDirectionAndAmount
,
2041 nsIEditor::EStripWrappers aStripWrappers
,
2042 AutoClonedSelectionRangeArray
& aRangesToDelete
,
2043 const WSRunScanner
& aWSRunScannerAtCaret
,
2044 const WSScanResult
& aScanFromCaretPointResult
,
2045 const Element
& aEditingHost
) {
2046 MOZ_ASSERT(aHTMLEditor
.IsTopLevelEditSubActionDataAvailable());
2047 MOZ_ASSERT(aRangesToDelete
.IsCollapsed());
2048 MOZ_ASSERT(aDirectionAndAmount
!= nsIEditor::eNone
);
2049 MOZ_ASSERT(aWSRunScannerAtCaret
.ScanStartRef().IsInContentNode());
2050 MOZ_ASSERT(EditorUtils::IsEditableContent(
2051 *aWSRunScannerAtCaret
.ScanStartRef().ContainerAs
<nsIContent
>(),
2054 if (StaticPrefs::editor_white_space_normalization_blink_compatible()) {
2055 if (aScanFromCaretPointResult
.InCollapsibleWhiteSpaces() ||
2056 aScanFromCaretPointResult
.InNonCollapsibleCharacters() ||
2057 aScanFromCaretPointResult
.ReachedPreformattedLineBreak()) {
2058 // This means that if aDirectionAndAmount == nsIEditor::eNext, collapse
2059 // selection at the found character. Otherwise, collapse selection after
2060 // the found character.
2061 nsresult rv
= aRangesToDelete
.Collapse(
2062 aScanFromCaretPointResult
.Point_Deprecated
<EditorRawDOMPoint
>());
2063 if (NS_FAILED(rv
)) {
2064 NS_WARNING("AutoClonedRangeArray::Collapse() failed");
2065 return Err(NS_ERROR_FAILURE
);
2067 Result
<CaretPoint
, nsresult
> caretPointOrError
=
2068 HandleDeleteTextAroundCollapsedRanges(
2069 aHTMLEditor
, aDirectionAndAmount
, aRangesToDelete
, aEditingHost
);
2070 if (MOZ_UNLIKELY(caretPointOrError
.isErr())) {
2072 "AutoDeleteRangesHandler::HandleDeleteTextAroundCollapsedRanges() "
2074 return caretPointOrError
.propagateErr();
2076 rv
= caretPointOrError
.unwrap().SuggestCaretPointTo(
2077 aHTMLEditor
, {SuggestCaret::OnlyIfHasSuggestion
,
2078 SuggestCaret::OnlyIfTransactionsAllowedToDoIt
,
2079 SuggestCaret::AndIgnoreTrivialError
});
2080 if (NS_FAILED(rv
)) {
2081 NS_WARNING("CaretPoint::SuggestCaretPointTo() failed");
2084 NS_WARNING_ASSERTION(
2085 rv
!= NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR
,
2086 "CaretPoint::SuggestCaretPoint() failed, but ignored");
2087 return EditActionResult::HandledResult();
2091 if (aScanFromCaretPointResult
.InCollapsibleWhiteSpaces() ||
2092 aScanFromCaretPointResult
.ReachedPreformattedLineBreak()) {
2093 Result
<CaretPoint
, nsresult
> caretPointOrError
=
2094 HandleDeleteCollapsedSelectionAtWhiteSpaces(
2095 aHTMLEditor
, aDirectionAndAmount
,
2096 aWSRunScannerAtCaret
.ScanStartRef(), aEditingHost
);
2097 if (MOZ_UNLIKELY(caretPointOrError
.isErr())) {
2099 "AutoDeleteRangesHandler::"
2100 "HandleDeleteCollapsedSelectionAtWhiteSpaces() failed");
2101 return caretPointOrError
.propagateErr();
2103 nsresult rv
= caretPointOrError
.unwrap().SuggestCaretPointTo(
2104 aHTMLEditor
, {SuggestCaret::OnlyIfHasSuggestion
});
2105 if (NS_FAILED(rv
)) {
2106 NS_WARNING("CaretPoint::SuggestCaretPointTo() failed");
2109 NS_WARNING_ASSERTION(
2110 rv
!= NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR
,
2111 "CaretPoint::SuggestCaretPointTo() failed, but ignored");
2112 return EditActionResult::HandledResult();
2115 if (aScanFromCaretPointResult
.InNonCollapsibleCharacters()) {
2116 if (NS_WARN_IF(!aScanFromCaretPointResult
.ContentIsText())) {
2117 return Err(NS_ERROR_FAILURE
);
2119 Result
<CaretPoint
, nsresult
> caretPointOrError
=
2120 HandleDeleteCollapsedSelectionAtVisibleChar(
2121 aHTMLEditor
, aDirectionAndAmount
, aRangesToDelete
,
2122 // This means that if aDirectionAndAmount == nsIEditor::eNext,
2123 // at the found character. Otherwise, after the found character.
2124 aScanFromCaretPointResult
.Point_Deprecated
<EditorDOMPoint
>(),
2126 if (MOZ_UNLIKELY(caretPointOrError
.isErr())) {
2128 "AutoDeleteRangesHandler::"
2129 "HandleDeleteCollapsedSelectionAtVisibleChar() failed");
2130 return caretPointOrError
.propagateErr();
2132 nsresult rv
= caretPointOrError
.unwrap().SuggestCaretPointTo(
2133 aHTMLEditor
, {SuggestCaret::OnlyIfHasSuggestion
});
2134 if (NS_FAILED(rv
)) {
2135 NS_WARNING("CaretPoint::SuggestCaretPointTo() failed");
2138 NS_WARNING_ASSERTION(
2139 rv
!= NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR
,
2140 "CaretPoint::SuggestCaretPointTo() failed, but ignored");
2141 return EditActionResult::HandledResult();
2144 if (aScanFromCaretPointResult
.ReachedSpecialContent() ||
2145 aScanFromCaretPointResult
.ReachedBRElement() ||
2146 aScanFromCaretPointResult
.ReachedHRElement() ||
2147 aScanFromCaretPointResult
.ReachedNonEditableOtherBlockElement()) {
2148 if (aScanFromCaretPointResult
.GetContent() == &aEditingHost
) {
2149 return EditActionResult::HandledResult();
2151 nsCOMPtr
<nsIContent
> atomicContent
= GetAtomicContentToDelete(
2152 aDirectionAndAmount
, aWSRunScannerAtCaret
, aScanFromCaretPointResult
);
2153 if (MOZ_UNLIKELY(!HTMLEditUtils::IsRemovableNode(*atomicContent
))) {
2155 "AutoDeleteRangesHandler::GetAtomicContentToDelete() cannot find "
2156 "removable atomic content");
2157 return Err(NS_ERROR_FAILURE
);
2159 Result
<CaretPoint
, nsresult
> caretPointOrError
= HandleDeleteAtomicContent(
2160 aHTMLEditor
, *atomicContent
, aWSRunScannerAtCaret
.ScanStartRef(),
2161 aWSRunScannerAtCaret
, aEditingHost
);
2162 if (MOZ_UNLIKELY(caretPointOrError
.isErr())) {
2163 NS_WARNING("AutoDeleteRangesHandler::HandleDeleteAtomicContent() failed");
2164 return caretPointOrError
.propagateErr();
2166 nsresult rv
= caretPointOrError
.unwrap().SuggestCaretPointTo(
2167 aHTMLEditor
, {SuggestCaret::OnlyIfHasSuggestion
});
2168 if (NS_FAILED(rv
)) {
2169 NS_WARNING("CaretPoint::SuggestCaretPointTo() failed");
2172 NS_WARNING_ASSERTION(
2173 rv
!= NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR
,
2174 "CaretPoint::SuggestCaretPointTo() failed, but ignored");
2175 return EditActionResult::HandledResult();
2178 if (aScanFromCaretPointResult
.ReachedOtherBlockElement()) {
2179 if (NS_WARN_IF(!aScanFromCaretPointResult
.ContentIsElement())) {
2180 return Err(NS_ERROR_FAILURE
);
2182 MOZ_ASSERT(!aRangesToDelete
.Ranges().IsEmpty());
2183 bool allRangesNotHandled
= true;
2184 auto ret
= EditActionResult::IgnoredResult();
2185 for (const OwningNonNull
<nsRange
>& range
: aRangesToDelete
.Ranges()) {
2186 AutoBlockElementsJoiner
joiner(*this);
2187 if (!joiner
.PrepareToDeleteAtOtherBlockBoundary(
2188 aHTMLEditor
, aDirectionAndAmount
,
2189 *aScanFromCaretPointResult
.ElementPtr(),
2190 aWSRunScannerAtCaret
.ScanStartRef(), aWSRunScannerAtCaret
)) {
2193 allRangesNotHandled
= false;
2194 Result
<EditActionResult
, nsresult
> result
=
2195 joiner
.Run(aHTMLEditor
, aDirectionAndAmount
, aStripWrappers
,
2196 aWSRunScannerAtCaret
.ScanStartRef(), MOZ_KnownLive(range
),
2198 if (MOZ_UNLIKELY(result
.isErr())) {
2200 "AutoBlockElementsJoiner::Run() failed (other block boundary)");
2203 ret
|= result
.inspect();
2205 return allRangesNotHandled
? EditActionResult::CanceledResult()
2209 if (aScanFromCaretPointResult
.ReachedCurrentBlockBoundary() ||
2210 aScanFromCaretPointResult
.ReachedInlineEditingHostBoundary()) {
2211 MOZ_ASSERT(aScanFromCaretPointResult
.ContentIsElement());
2212 MOZ_ASSERT(!aRangesToDelete
.Ranges().IsEmpty());
2213 bool allRangesNotHandled
= true;
2214 auto ret
= EditActionResult::IgnoredResult();
2215 for (const OwningNonNull
<nsRange
>& range
: aRangesToDelete
.Ranges()) {
2216 AutoBlockElementsJoiner
joiner(*this);
2217 if (!joiner
.PrepareToDeleteAtCurrentBlockBoundary(
2218 aHTMLEditor
, aDirectionAndAmount
,
2219 *aScanFromCaretPointResult
.ElementPtr(),
2220 aWSRunScannerAtCaret
.ScanStartRef(), aEditingHost
)) {
2223 allRangesNotHandled
= false;
2224 Result
<EditActionResult
, nsresult
> result
=
2225 joiner
.Run(aHTMLEditor
, aDirectionAndAmount
, aStripWrappers
,
2226 aWSRunScannerAtCaret
.ScanStartRef(), MOZ_KnownLive(range
),
2228 if (MOZ_UNLIKELY(result
.isErr())) {
2230 "AutoBlockElementsJoiner::Run() failed (current block boundary)");
2233 ret
|= result
.inspect();
2235 return allRangesNotHandled
? EditActionResult::CanceledResult()
2239 MOZ_ASSERT_UNREACHABLE("New type of reached content hasn't been handled yet");
2240 return EditActionResult::IgnoredResult();
2243 nsresult
HTMLEditor::AutoDeleteRangesHandler::
2244 ComputeRangesToDeleteTextAroundCollapsedRanges(
2245 nsIEditor::EDirection aDirectionAndAmount
,
2246 AutoClonedSelectionRangeArray
& aRangesToDelete
) const {
2247 MOZ_ASSERT(aDirectionAndAmount
== nsIEditor::eNext
||
2248 aDirectionAndAmount
== nsIEditor::ePrevious
);
2250 const auto caretPosition
=
2251 aRangesToDelete
.GetFirstRangeStartPoint
<EditorDOMPoint
>();
2252 MOZ_ASSERT(caretPosition
.IsSetAndValid());
2253 if (MOZ_UNLIKELY(NS_WARN_IF(!caretPosition
.IsInContentNode()))) {
2254 return NS_ERROR_FAILURE
;
2257 EditorDOMRangeInTexts rangeToDelete
;
2258 if (aDirectionAndAmount
== nsIEditor::eNext
) {
2259 Result
<EditorDOMRangeInTexts
, nsresult
> result
=
2260 WSRunScanner::GetRangeInTextNodesToForwardDeleteFrom(caretPosition
);
2261 if (result
.isErr()) {
2263 "WSRunScanner::GetRangeInTextNodesToForwardDeleteFrom() failed");
2264 return result
.unwrapErr();
2266 rangeToDelete
= result
.unwrap();
2267 if (!rangeToDelete
.IsPositioned()) {
2268 return NS_OK
; // no range to delete, but consume it.
2271 Result
<EditorDOMRangeInTexts
, nsresult
> result
=
2272 WSRunScanner::GetRangeInTextNodesToBackspaceFrom(caretPosition
);
2273 if (result
.isErr()) {
2274 NS_WARNING("WSRunScanner::GetRangeInTextNodesToBackspaceFrom() failed");
2275 return result
.unwrapErr();
2277 rangeToDelete
= result
.unwrap();
2278 if (!rangeToDelete
.IsPositioned()) {
2279 return NS_OK
; // no range to delete, but consume it.
2283 // FIXME: If we'll delete unnecessary following <br>, we need to include it
2284 // into aRangesToDelete.
2286 nsresult rv
= aRangesToDelete
.SetStartAndEnd(rangeToDelete
.StartRef(),
2287 rangeToDelete
.EndRef());
2288 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
2289 "AutoArrayRanges::SetStartAndEnd() failed");
2293 Result
<CaretPoint
, nsresult
>
2294 HTMLEditor::AutoDeleteRangesHandler::HandleDeleteTextAroundCollapsedRanges(
2295 HTMLEditor
& aHTMLEditor
, nsIEditor::EDirection aDirectionAndAmount
,
2296 AutoClonedSelectionRangeArray
& aRangesToDelete
,
2297 const Element
& aEditingHost
) {
2298 MOZ_ASSERT(aHTMLEditor
.IsEditActionDataAvailable());
2299 MOZ_ASSERT(aDirectionAndAmount
== nsIEditor::eNext
||
2300 aDirectionAndAmount
== nsIEditor::ePrevious
);
2302 nsresult rv
= ComputeRangesToDeleteTextAroundCollapsedRanges(
2303 aDirectionAndAmount
, aRangesToDelete
);
2304 if (NS_FAILED(rv
)) {
2305 return Err(NS_ERROR_FAILURE
);
2307 if (MOZ_UNLIKELY(aRangesToDelete
.IsCollapsed())) {
2308 return CaretPoint(EditorDOMPoint()); // no range to delete
2311 // FYI: rangeToDelete does not contain newly empty inline ancestors which
2312 // are removed by DeleteTextAndNormalizeSurroundingWhiteSpaces().
2313 // So, if `getTargetRanges()` needs to include parent empty elements,
2314 // we need to extend the range with
2315 // HTMLEditUtils::GetMostDistantAncestorEditableEmptyInlineElement().
2316 EditorRawDOMRange
rangeToDelete(aRangesToDelete
.FirstRangeRef());
2317 if (MOZ_UNLIKELY(!rangeToDelete
.IsInTextNodes())) {
2318 NS_WARNING("The extended range to delete character was not in text nodes");
2319 return Err(NS_ERROR_FAILURE
);
2322 Result
<CaretPoint
, nsresult
> caretPointOrError
=
2323 aHTMLEditor
.DeleteTextAndNormalizeSurroundingWhiteSpaces(
2324 rangeToDelete
.StartRef().AsInText(),
2325 rangeToDelete
.EndRef().AsInText(),
2326 TreatEmptyTextNodes::RemoveAllEmptyInlineAncestors
,
2327 aDirectionAndAmount
== nsIEditor::eNext
? DeleteDirection::Forward
2328 : DeleteDirection::Backward
,
2330 aHTMLEditor
.TopLevelEditSubActionDataRef().mDidNormalizeWhitespaces
= true;
2331 NS_WARNING_ASSERTION(
2332 caretPointOrError
.isOk(),
2333 "HTMLEditor::DeleteTextAndNormalizeSurroundingWhiteSpaces() failed");
2334 return caretPointOrError
;
2337 Result
<CaretPoint
, nsresult
> HTMLEditor::AutoDeleteRangesHandler::
2338 HandleDeleteCollapsedSelectionAtWhiteSpaces(
2339 HTMLEditor
& aHTMLEditor
, nsIEditor::EDirection aDirectionAndAmount
,
2340 const EditorDOMPoint
& aPointToDelete
, const Element
& aEditingHost
) {
2341 MOZ_ASSERT(aHTMLEditor
.IsEditActionDataAvailable());
2342 MOZ_ASSERT(!StaticPrefs::editor_white_space_normalization_blink_compatible());
2344 EditorDOMPoint pointToPutCaret
;
2345 if (aDirectionAndAmount
== nsIEditor::eNext
) {
2346 Result
<CaretPoint
, nsresult
> caretPointOrError
=
2347 WhiteSpaceVisibilityKeeper::DeleteInclusiveNextWhiteSpace(
2348 aHTMLEditor
, aPointToDelete
, aEditingHost
);
2349 if (MOZ_UNLIKELY(caretPointOrError
.isErr())) {
2351 "WhiteSpaceVisibilityKeeper::DeleteInclusiveNextWhiteSpace() failed");
2352 return caretPointOrError
;
2354 caretPointOrError
.unwrap().MoveCaretPointTo(
2355 pointToPutCaret
, aHTMLEditor
,
2356 {SuggestCaret::OnlyIfHasSuggestion
,
2357 SuggestCaret::OnlyIfTransactionsAllowedToDoIt
});
2359 Result
<CaretPoint
, nsresult
> caretPointOrError
=
2360 WhiteSpaceVisibilityKeeper::DeletePreviousWhiteSpace(
2361 aHTMLEditor
, aPointToDelete
, aEditingHost
);
2362 if (MOZ_UNLIKELY(caretPointOrError
.isErr())) {
2364 "WhiteSpaceVisibilityKeeper::DeletePreviousWhiteSpace() failed");
2365 return caretPointOrError
;
2367 caretPointOrError
.unwrap().MoveCaretPointTo(
2368 pointToPutCaret
, aHTMLEditor
,
2369 {SuggestCaret::OnlyIfHasSuggestion
,
2370 SuggestCaret::OnlyIfTransactionsAllowedToDoIt
});
2373 if (MOZ_LIKELY(pointToPutCaret
.IsInContentNode())) {
2374 AutoTrackDOMPoint
trackPointToPutCaret(aHTMLEditor
.RangeUpdaterRef(),
2376 nsresult rv
= aHTMLEditor
.EnsureNoFollowingUnnecessaryLineBreak(
2377 pointToPutCaret
, aEditingHost
);
2378 if (NS_FAILED(rv
)) {
2379 NS_WARNING("HTMLEditor::EnsureNoFollowingUnnecessaryLineBreak() failed");
2382 trackPointToPutCaret
.FlushAndStopTracking();
2383 if (NS_WARN_IF(!pointToPutCaret
.IsSet())) {
2384 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
);
2388 auto newCaretPosition
=
2389 aHTMLEditor
.GetFirstSelectionStartPoint
<EditorDOMPoint
>();
2390 if (MOZ_UNLIKELY(!newCaretPosition
.IsSet())) {
2391 NS_WARNING("There was no selection range");
2392 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
);
2394 const bool isDeleteSelection
= aHTMLEditor
.GetTopLevelEditSubAction() ==
2395 EditSubAction::eDeleteSelectedContent
;
2396 AutoTrackDOMPoint
trackCaretPoint(aHTMLEditor
.RangeUpdaterRef(),
2398 if (isDeleteSelection
) {
2399 // Don't remove empty inline elements in the plaintext-only mode because
2400 // nobody can restore the style again.
2401 if (MOZ_LIKELY(newCaretPosition
.IsInContentNode()) &&
2402 !aEditingHost
.IsContentEditablePlainTextOnly()) {
2403 Result
<CaretPoint
, nsresult
> caretPointOrError
=
2404 aHTMLEditor
.DeleteEmptyInclusiveAncestorInlineElements(
2405 MOZ_KnownLive(*newCaretPosition
.ContainerAs
<nsIContent
>()),
2407 if (MOZ_UNLIKELY(caretPointOrError
.isErr())) {
2409 "HTMLEditor::DeleteEmptyInclusiveAncestorInlineElements() failed");
2410 return caretPointOrError
.propagateErr();
2412 caretPointOrError
.unwrap().MoveCaretPointTo(
2413 newCaretPosition
, {SuggestCaret::OnlyIfHasSuggestion
});
2414 if (NS_WARN_IF(!newCaretPosition
.IsSetAndValidInComposedDoc())) {
2415 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
);
2419 if ((aHTMLEditor
.IsMailEditor() || aHTMLEditor
.IsPlaintextMailComposer()) &&
2420 MOZ_LIKELY(newCaretPosition
.IsInContentNode())) {
2421 AutoTrackDOMPoint
trackNewCaretPosition(aHTMLEditor
.RangeUpdaterRef(),
2423 nsresult rv
= aHTMLEditor
.DeleteMostAncestorMailCiteElementIfEmpty(
2424 MOZ_KnownLive(*newCaretPosition
.ContainerAs
<nsIContent
>()));
2425 if (NS_FAILED(rv
)) {
2427 "HTMLEditor::DeleteMostAncestorMailCiteElementIfEmpty() failed");
2430 trackNewCaretPosition
.FlushAndStopTracking();
2431 if (NS_WARN_IF(!newCaretPosition
.IsSetAndValidInComposedDoc())) {
2432 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
);
2435 if (isDeleteSelection
) {
2436 Result
<CreateLineBreakResult
, nsresult
> insertPaddingBRElementOrError
=
2437 aHTMLEditor
.InsertPaddingBRElementIfNeeded(
2439 aEditingHost
.IsContentEditablePlainTextOnly() ? nsIEditor::eNoStrip
2440 : nsIEditor::eStrip
,
2442 if (MOZ_UNLIKELY(insertPaddingBRElementOrError
.isErr())) {
2443 NS_WARNING("HTMLEditor::InsertPaddingBRElementIfNeeded() failed");
2444 return insertPaddingBRElementOrError
.propagateErr();
2446 trackCaretPoint
.FlushAndStopTracking();
2447 if (!pointToPutCaret
.IsInTextNode()) {
2448 insertPaddingBRElementOrError
.unwrap().MoveCaretPointTo(
2449 pointToPutCaret
, {SuggestCaret::OnlyIfHasSuggestion
});
2451 insertPaddingBRElementOrError
.unwrap().IgnoreCaretPointSuggestion();
2454 trackCaretPoint
.FlushAndStopTracking();
2455 return CaretPoint(std::move(pointToPutCaret
));
2458 Result
<CaretPoint
, nsresult
> HTMLEditor::AutoDeleteRangesHandler::
2459 HandleDeleteCollapsedSelectionAtVisibleChar(
2460 HTMLEditor
& aHTMLEditor
, nsIEditor::EDirection aDirectionAndAmount
,
2461 AutoClonedSelectionRangeArray
& aRangesToDelete
,
2462 const EditorDOMPoint
& aPointAtDeletingChar
,
2463 const Element
& aEditingHost
) {
2464 MOZ_ASSERT(aHTMLEditor
.IsTopLevelEditSubActionDataAvailable());
2465 MOZ_ASSERT(!StaticPrefs::editor_white_space_normalization_blink_compatible());
2466 MOZ_ASSERT(aPointAtDeletingChar
.IsSet());
2467 MOZ_ASSERT(aPointAtDeletingChar
.IsInTextNode());
2469 OwningNonNull
<Text
> visibleTextNode
=
2470 *aPointAtDeletingChar
.ContainerAs
<Text
>();
2471 EditorDOMPoint startToDelete
, endToDelete
;
2472 // FIXME: This does not care grapheme cluster of complicate character
2473 // sequence like Emoji.
2474 // TODO: Investigate what happens if a grapheme cluster which should be
2475 // delete once is split to multiple text nodes.
2476 // TODO: We should stop using this path, instead, we should extend the range
2477 // before calling this method.
2478 if (aDirectionAndAmount
== nsIEditor::ePrevious
) {
2479 if (MOZ_UNLIKELY(aPointAtDeletingChar
.IsStartOfContainer())) {
2480 return Err(NS_ERROR_UNEXPECTED
);
2482 startToDelete
= aPointAtDeletingChar
.PreviousPoint();
2483 endToDelete
= aPointAtDeletingChar
;
2484 // Bug 1068979: delete both codepoints if surrogate pair
2485 if (!startToDelete
.IsStartOfContainer()) {
2486 const nsTextFragment
* text
= &visibleTextNode
->TextFragment();
2487 if (text
->IsLowSurrogateFollowingHighSurrogateAt(
2488 startToDelete
.Offset())) {
2489 startToDelete
.RewindOffset();
2493 if (NS_WARN_IF(aRangesToDelete
.Ranges().IsEmpty()) ||
2494 NS_WARN_IF(aRangesToDelete
.FirstRangeRef()->GetStartContainer() !=
2495 aPointAtDeletingChar
.GetContainer()) ||
2496 NS_WARN_IF(aRangesToDelete
.FirstRangeRef()->GetEndContainer() !=
2497 aPointAtDeletingChar
.GetContainer())) {
2498 return Err(NS_ERROR_FAILURE
);
2500 startToDelete
= aRangesToDelete
.FirstRangeRef()->StartRef();
2501 endToDelete
= aRangesToDelete
.FirstRangeRef()->EndRef();
2505 Result
<CaretPoint
, nsresult
> caretPointOrError
=
2506 WhiteSpaceVisibilityKeeper::PrepareToDeleteRangeAndTrackPoints(
2507 aHTMLEditor
, &startToDelete
, &endToDelete
, aEditingHost
);
2508 if (MOZ_UNLIKELY(caretPointOrError
.isErr())) {
2510 "WhiteSpaceVisibilityKeeper::PrepareToDeleteRangeAndTrackPoints() "
2512 return caretPointOrError
.propagateErr();
2514 // Ignore caret position because we'll set caret position below
2515 caretPointOrError
.unwrap().IgnoreCaretPointSuggestion();
2518 if (aHTMLEditor
.MayHaveMutationEventListeners(
2519 NS_EVENT_BITS_MUTATION_NODEREMOVED
|
2520 NS_EVENT_BITS_MUTATION_NODEREMOVEDFROMDOCUMENT
|
2521 NS_EVENT_BITS_MUTATION_ATTRMODIFIED
|
2522 NS_EVENT_BITS_MUTATION_CHARACTERDATAMODIFIED
) &&
2523 (NS_WARN_IF(!startToDelete
.IsSetAndValid()) ||
2524 NS_WARN_IF(!startToDelete
.IsInTextNode()) ||
2525 NS_WARN_IF(!endToDelete
.IsSetAndValid()) ||
2526 NS_WARN_IF(!endToDelete
.IsInTextNode()) ||
2527 NS_WARN_IF(startToDelete
.ContainerAs
<Text
>() != visibleTextNode
) ||
2528 NS_WARN_IF(endToDelete
.ContainerAs
<Text
>() != visibleTextNode
) ||
2529 NS_WARN_IF(startToDelete
.Offset() >= endToDelete
.Offset()))) {
2530 NS_WARNING("Mutation event listener changed the DOM tree");
2531 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
);
2534 EditorDOMPoint pointToPutCaret
= startToDelete
;
2536 AutoTrackDOMPoint
trackPointToPutCaret(aHTMLEditor
.RangeUpdaterRef(),
2538 Result
<CaretPoint
, nsresult
> caretPointOrError
=
2539 aHTMLEditor
.DeleteTextWithTransaction(
2540 visibleTextNode
, startToDelete
.Offset(),
2541 endToDelete
.Offset() - startToDelete
.Offset());
2542 if (MOZ_UNLIKELY(caretPointOrError
.isErr())) {
2543 NS_WARNING("HTMLEditor::DeleteTextWithTransaction() failed");
2544 return caretPointOrError
.propagateErr();
2546 trackPointToPutCaret
.FlushAndStopTracking();
2547 caretPointOrError
.unwrap().MoveCaretPointTo(
2548 pointToPutCaret
, aHTMLEditor
, {SuggestCaret::OnlyIfHasSuggestion
});
2551 // XXX When Backspace key is pressed, Chromium removes following empty
2552 // text nodes when removing the last character of the non-empty text
2553 // node. However, Edge never removes empty text nodes even if
2554 // selection is in the following empty text node(s). For now, we
2555 // should keep our traditional behavior same as Edge for backward
2557 // XXX When Delete key is pressed, Edge removes all preceding empty
2558 // text nodes when removing the first character of the non-empty
2559 // text node. Chromium removes only selected empty text node and
2560 // following empty text nodes and the first character of the
2561 // non-empty text node. For now, we should keep our traditional
2562 // behavior same as Chromium for backward compatibility.
2564 AutoTrackDOMPoint
trackPointToPutCaret(aHTMLEditor
.RangeUpdaterRef(),
2567 DeleteNodeIfInvisibleAndEditableTextNode(aHTMLEditor
, visibleTextNode
);
2568 if (NS_WARN_IF(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
2569 return Err(NS_ERROR_EDITOR_DESTROYED
);
2571 NS_WARNING_ASSERTION(
2573 "AutoDeleteRangesHandler::DeleteNodeIfInvisibleAndEditableTextNode() "
2574 "failed, but ignored");
2577 if (NS_WARN_IF(!pointToPutCaret
.IsSet())) {
2578 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
);
2581 if (MOZ_LIKELY(pointToPutCaret
.IsInContentNode())) {
2582 AutoTrackDOMPoint
trackPointToPutCaret(aHTMLEditor
.RangeUpdaterRef(),
2584 nsresult rv
= aHTMLEditor
.EnsureNoFollowingUnnecessaryLineBreak(
2585 pointToPutCaret
, aEditingHost
);
2586 if (NS_FAILED(rv
)) {
2587 NS_WARNING("HTMLEditor::EnsureNoFollowingUnnecessaryLineBreak() failed");
2591 if (NS_WARN_IF(!pointToPutCaret
.IsSet())) {
2592 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
);
2595 // XXX `Selection` may be modified by mutation event listeners so
2596 // that we should use EditorDOMPoint::AtEndOf(visibleTextNode)
2597 // instead. (Perhaps, we don't and/or shouldn't need to do this
2598 // if the text node is preformatted.)
2599 const bool isDeleteSelection
= aHTMLEditor
.GetTopLevelEditSubAction() ==
2600 EditSubAction::eDeleteSelectedContent
;
2601 if (isDeleteSelection
) {
2602 // Don't remove empty inline elements in the plaintext-only mode because
2603 // nobody can restore the style again.
2604 if (MOZ_LIKELY(pointToPutCaret
.IsInContentNode()) &&
2605 !aEditingHost
.IsContentEditablePlainTextOnly()) {
2606 AutoTrackDOMPoint
trackPointToPutCaret(aHTMLEditor
.RangeUpdaterRef(),
2608 Result
<CaretPoint
, nsresult
> caretPointOrError
=
2609 aHTMLEditor
.DeleteEmptyInclusiveAncestorInlineElements(
2610 MOZ_KnownLive(*pointToPutCaret
.ContainerAs
<nsIContent
>()),
2612 if (MOZ_UNLIKELY(caretPointOrError
.isErr())) {
2614 "HTMLEditor::DeleteEmptyInclusiveAncestorInlineElements() failed");
2615 return caretPointOrError
;
2617 trackPointToPutCaret
.FlushAndStopTracking();
2618 caretPointOrError
.unwrap().MoveCaretPointTo(
2619 pointToPutCaret
, {SuggestCaret::OnlyIfHasSuggestion
});
2620 if (NS_WARN_IF(!pointToPutCaret
.IsSetAndValidInComposedDoc())) {
2621 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
);
2626 if ((aHTMLEditor
.IsMailEditor() || aHTMLEditor
.IsPlaintextMailComposer()) &&
2627 MOZ_LIKELY(pointToPutCaret
.IsInContentNode())) {
2628 AutoTrackDOMPoint
trackPointToPutCaret(aHTMLEditor
.RangeUpdaterRef(),
2630 nsresult rv
= aHTMLEditor
.DeleteMostAncestorMailCiteElementIfEmpty(
2631 MOZ_KnownLive(*pointToPutCaret
.ContainerAs
<nsIContent
>()));
2632 if (NS_FAILED(rv
)) {
2634 "HTMLEditor::DeleteMostAncestorMailCiteElementIfEmpty() failed");
2637 trackPointToPutCaret
.FlushAndStopTracking();
2638 if (NS_WARN_IF(!pointToPutCaret
.IsSetAndValidInComposedDoc())) {
2639 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
);
2643 if (isDeleteSelection
) {
2644 AutoTrackDOMPoint
trackPointToPutCaret(aHTMLEditor
.RangeUpdaterRef(),
2646 Result
<CreateLineBreakResult
, nsresult
> insertPaddingBRElementOrError
=
2647 aHTMLEditor
.InsertPaddingBRElementIfNeeded(
2649 aEditingHost
.IsContentEditablePlainTextOnly() ? nsIEditor::eNoStrip
2650 : nsIEditor::eStrip
,
2652 if (MOZ_UNLIKELY(insertPaddingBRElementOrError
.isErr())) {
2653 NS_WARNING("HTMLEditor::InsertPaddingBRElementIfNeeded() failed");
2654 return insertPaddingBRElementOrError
.propagateErr();
2656 trackPointToPutCaret
.FlushAndStopTracking();
2657 if (!pointToPutCaret
.IsInTextNode()) {
2658 insertPaddingBRElementOrError
.unwrap().MoveCaretPointTo(
2659 pointToPutCaret
, {SuggestCaret::OnlyIfHasSuggestion
});
2661 insertPaddingBRElementOrError
.unwrap().IgnoreCaretPointSuggestion();
2664 // Remember that we did a ranged delete for the benefit of
2665 // AfterEditInner().
2666 aHTMLEditor
.TopLevelEditSubActionDataRef().mDidDeleteNonCollapsedRange
= true;
2667 return CaretPoint(std::move(pointToPutCaret
));
2671 nsIContent
* HTMLEditor::AutoDeleteRangesHandler::GetAtomicContentToDelete(
2672 nsIEditor::EDirection aDirectionAndAmount
,
2673 const WSRunScanner
& aWSRunScannerAtCaret
,
2674 const WSScanResult
& aScanFromCaretPointResult
) {
2675 MOZ_ASSERT(aScanFromCaretPointResult
.GetContent());
2677 if (!aScanFromCaretPointResult
.ReachedSpecialContent()) {
2678 return aScanFromCaretPointResult
.GetContent();
2681 if (!aScanFromCaretPointResult
.GetContent()->IsText() ||
2682 HTMLEditUtils::IsRemovableNode(*aScanFromCaretPointResult
.GetContent())) {
2683 return aScanFromCaretPointResult
.GetContent();
2686 // aScanFromCaretPointResult is non-removable text node.
2687 // Since we try removing atomic content, we look for removable node from
2688 // scanned point that is non-removable text.
2689 nsIContent
* removableRoot
= aScanFromCaretPointResult
.GetContent();
2690 while (removableRoot
&& !HTMLEditUtils::IsRemovableNode(*removableRoot
)) {
2691 removableRoot
= removableRoot
->GetParent();
2694 if (removableRoot
) {
2695 return removableRoot
;
2698 // Not found better content. This content may not be removable.
2699 return aScanFromCaretPointResult
.GetContent();
2703 HTMLEditor::AutoDeleteRangesHandler::ComputeRangesToDeleteAtomicContent(
2704 const nsIContent
& aAtomicContent
,
2705 AutoClonedSelectionRangeArray
& aRangesToDelete
) const {
2706 EditorDOMRange rangeToDelete
=
2707 WSRunScanner::GetRangesForDeletingAtomicContent(aAtomicContent
);
2708 if (!rangeToDelete
.IsPositioned()) {
2709 NS_WARNING("WSRunScanner::GetRangeForDeleteAContentNode() failed");
2710 return NS_ERROR_FAILURE
;
2713 // FIXME: If we'll delete unnecessary following <br>, we need to include it
2714 // into aRangesToDelete.
2716 nsresult rv
= aRangesToDelete
.SetStartAndEnd(rangeToDelete
.StartRef(),
2717 rangeToDelete
.EndRef());
2718 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
2719 "AutoClonedRangeArray::SetStartAndEnd() failed");
2723 Result
<CaretPoint
, nsresult
>
2724 HTMLEditor::AutoDeleteRangesHandler::HandleDeleteAtomicContent(
2725 HTMLEditor
& aHTMLEditor
, nsIContent
& aAtomicContent
,
2726 const EditorDOMPoint
& aCaretPoint
, const WSRunScanner
& aWSRunScannerAtCaret
,
2727 const Element
& aEditingHost
) {
2728 MOZ_ASSERT(aHTMLEditor
.IsEditActionDataAvailable());
2729 MOZ_ASSERT(!HTMLEditUtils::IsInvisibleBRElement(aAtomicContent
));
2730 MOZ_ASSERT(&aAtomicContent
!= aWSRunScannerAtCaret
.GetEditingHost());
2732 EditorDOMPoint pointToPutCaret
= aCaretPoint
;
2734 AutoTrackDOMPoint
trackPointToPutCaret(aHTMLEditor
.RangeUpdaterRef(),
2736 Result
<CaretPoint
, nsresult
> caretPointOrError
=
2737 WhiteSpaceVisibilityKeeper::DeleteContentNodeAndJoinTextNodesAroundIt(
2738 aHTMLEditor
, aAtomicContent
, aCaretPoint
, aEditingHost
);
2739 if (MOZ_UNLIKELY(caretPointOrError
.isErr())) {
2741 "WhiteSpaceVisibilityKeeper::"
2742 "DeleteContentNodeAndJoinTextNodesAroundIt() failed");
2743 return caretPointOrError
;
2745 trackPointToPutCaret
.FlushAndStopTracking();
2746 caretPointOrError
.unwrap().MoveCaretPointTo(
2747 pointToPutCaret
, aHTMLEditor
,
2748 {SuggestCaret::OnlyIfHasSuggestion
,
2749 SuggestCaret::OnlyIfTransactionsAllowedToDoIt
});
2750 if (NS_WARN_IF(!pointToPutCaret
.IsSet())) {
2751 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
);
2755 if (MOZ_LIKELY(pointToPutCaret
.IsInContentNode())) {
2756 AutoTrackDOMPoint
trackPointToPutCaret(aHTMLEditor
.RangeUpdaterRef(),
2758 nsresult rv
= aHTMLEditor
.EnsureNoFollowingUnnecessaryLineBreak(
2759 pointToPutCaret
, aEditingHost
);
2760 if (NS_FAILED(rv
)) {
2761 NS_WARNING("HTMLEditor::EnsureNoFollowingUnnecessaryLineBreak() failed");
2765 if (NS_WARN_IF(!pointToPutCaret
.IsSet())) {
2766 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
);
2769 if ((aHTMLEditor
.IsMailEditor() || aHTMLEditor
.IsPlaintextMailComposer()) &&
2770 MOZ_LIKELY(pointToPutCaret
.IsInContentNode())) {
2771 AutoTrackDOMPoint
trackPointToPutCaret(aHTMLEditor
.RangeUpdaterRef(),
2773 nsresult rv
= aHTMLEditor
.DeleteMostAncestorMailCiteElementIfEmpty(
2774 MOZ_KnownLive(*pointToPutCaret
.ContainerAs
<nsIContent
>()));
2775 if (NS_FAILED(rv
)) {
2777 "HTMLEditor::DeleteMostAncestorMailCiteElementIfEmpty() failed");
2780 trackPointToPutCaret
.FlushAndStopTracking();
2781 if (NS_WARN_IF(!pointToPutCaret
.IsSetAndValidInComposedDoc())) {
2782 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
);
2786 if (aHTMLEditor
.GetTopLevelEditSubAction() ==
2787 EditSubAction::eDeleteSelectedContent
) {
2788 AutoTrackDOMPoint
trackPointToPutCaret(aHTMLEditor
.RangeUpdaterRef(),
2790 Result
<CreateLineBreakResult
, nsresult
> insertPaddingBRElementOrError
=
2791 aHTMLEditor
.InsertPaddingBRElementIfNeeded(
2793 aEditingHost
.IsContentEditablePlainTextOnly() ? nsIEditor::eNoStrip
2794 : nsIEditor::eStrip
,
2796 if (MOZ_UNLIKELY(insertPaddingBRElementOrError
.isErr())) {
2797 NS_WARNING("HTMLEditor::InsertPaddingBRElementIfNeeded() failed");
2798 return insertPaddingBRElementOrError
.propagateErr();
2800 trackPointToPutCaret
.FlushAndStopTracking();
2801 if (!pointToPutCaret
.IsInTextNode()) {
2802 insertPaddingBRElementOrError
.unwrap().MoveCaretPointTo(
2803 pointToPutCaret
, aHTMLEditor
, {SuggestCaret::OnlyIfHasSuggestion
});
2804 if (NS_WARN_IF(!pointToPutCaret
.IsSet())) {
2805 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
);
2808 insertPaddingBRElementOrError
.unwrap().IgnoreCaretPointSuggestion();
2809 if (NS_WARN_IF(!pointToPutCaret
.IsSet())) {
2810 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
);
2814 return CaretPoint(std::move(pointToPutCaret
));
2818 Result
<bool, nsresult
> HTMLEditor::AutoDeleteRangesHandler::
2819 ExtendRangeToContainAncestorInlineElementsAtStart(
2820 nsRange
& aRangeToDelete
, const Element
& aEditingHost
) {
2821 MOZ_ASSERT(aRangeToDelete
.IsPositioned());
2822 MOZ_ASSERT(aRangeToDelete
.GetCommonAncestorContainer(IgnoreErrors()));
2823 MOZ_ASSERT(aRangeToDelete
.GetCommonAncestorContainer(IgnoreErrors())
2824 ->IsInclusiveDescendantOf(&aEditingHost
));
2826 EditorRawDOMPoint
startPoint(aRangeToDelete
.StartRef());
2827 if (startPoint
.IsInTextNode()) {
2828 if (!startPoint
.IsStartOfContainer()) {
2829 // FIXME: If before the point has only collapsible white-spaces and the
2830 // text node follows a block boundary, we should treat the range start
2831 // from start of the text node.
2834 startPoint
.Set(startPoint
.ContainerAs
<Text
>());
2835 if (NS_WARN_IF(!startPoint
.IsSet())) {
2836 return Err(NS_ERROR_FAILURE
);
2838 if (startPoint
.GetContainer() == &aEditingHost
) {
2841 } else if (startPoint
.IsInDataNode()) {
2842 startPoint
.Set(startPoint
.ContainerAs
<nsIContent
>());
2843 if (NS_WARN_IF(!startPoint
.IsSet())) {
2844 return Err(NS_ERROR_FAILURE
);
2846 if (startPoint
.GetContainer() == &aEditingHost
) {
2849 } else if (startPoint
.GetContainer() == &aEditingHost
) {
2853 // FYI: This method is designed for deleting inline elements which become
2854 // empty if aRangeToDelete which crosses a block boundary of right block
2855 // child. Therefore, you may need to improve this method if you want to use
2856 // this in the other cases.
2858 nsINode
* const commonAncestor
=
2859 nsContentUtils::GetClosestCommonInclusiveAncestor(
2860 startPoint
.GetContainer(), aRangeToDelete
.GetEndContainer());
2861 if (NS_WARN_IF(!commonAncestor
)) {
2862 return Err(NS_ERROR_FAILURE
);
2864 MOZ_ASSERT(commonAncestor
->IsInclusiveDescendantOf(&aEditingHost
));
2866 EditorRawDOMPoint
newStartPoint(startPoint
);
2867 while (newStartPoint
.GetContainer() != &aEditingHost
&&
2868 newStartPoint
.GetContainer() != commonAncestor
) {
2869 if (NS_WARN_IF(!newStartPoint
.IsInContentNode())) {
2870 return Err(NS_ERROR_FAILURE
);
2872 if (!HTMLEditUtils::IsInlineContent(
2873 *newStartPoint
.ContainerAs
<nsIContent
>(),
2874 BlockInlineCheck::UseComputedDisplayOutsideStyle
)) {
2877 // The container is inline, check whether the point is first visible point
2878 // or not to consider whether climbing up the tree.
2879 bool foundVisiblePrevSibling
= false;
2880 for (nsIContent
* content
= newStartPoint
.GetPreviousSiblingOfChild();
2881 content
; content
= content
->GetPreviousSibling()) {
2882 if (Text
* text
= Text::FromNode(content
)) {
2883 if (HTMLEditUtils::IsVisibleTextNode(*text
)) {
2884 foundVisiblePrevSibling
= true;
2887 // The text node is invisible.
2888 } else if (content
->IsComment()) {
2889 // Ignore the comment node.
2890 } else if (!HTMLEditUtils::IsInlineContent(
2892 BlockInlineCheck::UseComputedDisplayOutsideStyle
) ||
2893 !HTMLEditUtils::IsEmptyNode(
2895 {EmptyCheckOption::TreatSingleBRElementAsVisible
})) {
2896 foundVisiblePrevSibling
= true;
2900 if (foundVisiblePrevSibling
) {
2903 // the point can be treated as start of the parent inline now.
2904 newStartPoint
.Set(newStartPoint
.ContainerAs
<nsIContent
>());
2905 if (NS_WARN_IF(!newStartPoint
.IsSet())) {
2906 return Err(NS_ERROR_FAILURE
);
2909 if (newStartPoint
== startPoint
) {
2910 return false; // Don't need to modify the range
2912 IgnoredErrorResult error
;
2913 aRangeToDelete
.SetStart(newStartPoint
.ToRawRangeBoundary(), error
);
2914 if (MOZ_UNLIKELY(error
.Failed())) {
2915 return Err(NS_ERROR_FAILURE
);
2920 bool HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
2921 PrepareToDeleteAtOtherBlockBoundary(
2922 const HTMLEditor
& aHTMLEditor
,
2923 nsIEditor::EDirection aDirectionAndAmount
, Element
& aOtherBlockElement
,
2924 const EditorDOMPoint
& aCaretPoint
,
2925 const WSRunScanner
& aWSRunScannerAtCaret
) {
2926 MOZ_ASSERT(aHTMLEditor
.IsEditActionDataAvailable());
2927 MOZ_ASSERT(aCaretPoint
.IsSetAndValid());
2929 mMode
= Mode::JoinOtherBlock
;
2931 // Make sure it's not a table element. If so, cancel the operation
2932 // (translation: users cannot backspace or delete across table cells)
2933 if (HTMLEditUtils::IsAnyTableElement(&aOtherBlockElement
)) {
2937 // First find the adjacent node in the block
2938 if (aDirectionAndAmount
== nsIEditor::ePrevious
) {
2939 mLeafContentInOtherBlock
= HTMLEditUtils::GetLastLeafContent(
2940 aOtherBlockElement
, {LeafNodeType::OnlyEditableLeafNode
},
2941 BlockInlineCheck::Unused
, &aOtherBlockElement
);
2942 mLeftContent
= mLeafContentInOtherBlock
;
2943 mRightContent
= aCaretPoint
.GetContainerAs
<nsIContent
>();
2945 mLeafContentInOtherBlock
= HTMLEditUtils::GetFirstLeafContent(
2946 aOtherBlockElement
, {LeafNodeType::OnlyEditableLeafNode
},
2947 BlockInlineCheck::Unused
, &aOtherBlockElement
);
2948 mLeftContent
= aCaretPoint
.GetContainerAs
<nsIContent
>();
2949 mRightContent
= mLeafContentInOtherBlock
;
2952 // Next to a block. See if we are between the block and a `<br>`.
2953 // If so, we really want to delete the `<br>`. Else join content at
2954 // selection to the block.
2955 const WSScanResult scanFromCaretResult
=
2956 aDirectionAndAmount
== nsIEditor::eNext
2957 ? aWSRunScannerAtCaret
.ScanPreviousVisibleNodeOrBlockBoundaryFrom(
2959 : aWSRunScannerAtCaret
2960 .ScanInclusiveNextVisibleNodeOrBlockBoundaryFrom(aCaretPoint
);
2961 // If we found a `<br>` element, we need to delete it instead of joining the
2963 if (scanFromCaretResult
.ReachedBRElement()) {
2964 mBRElement
= scanFromCaretResult
.BRElementPtr();
2965 mMode
= Mode::DeleteBRElement
;
2969 return mLeftContent
&& mRightContent
;
2972 nsresult
HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
2973 ComputeRangeToDeleteLineBreak(const HTMLEditor
& aHTMLEditor
,
2974 nsRange
& aRangeToDelete
,
2975 const Element
& aEditingHost
,
2976 ComputeRangeFor aComputeRangeFor
) const {
2977 // FIXME: Scan invisible leading white-spaces after the <br>.
2978 MOZ_ASSERT_IF(mMode
== Mode::DeleteBRElement
, mBRElement
);
2979 MOZ_ASSERT_IF(mMode
== Mode::DeletePrecedingBRElementOfBlock
, mBRElement
);
2980 MOZ_ASSERT_IF(mMode
== Mode::DeletePrecedingPreformattedLineBreak
,
2981 mPreformattedLineBreak
.IsSetAndValid());
2982 MOZ_ASSERT_IF(mMode
== Mode::DeletePrecedingPreformattedLineBreak
,
2983 mPreformattedLineBreak
.IsCharPreformattedNewLine());
2984 MOZ_ASSERT_IF(aComputeRangeFor
== ComputeRangeFor::GetTargetRanges
,
2985 aRangeToDelete
.IsPositioned());
2987 // If we're computing for beforeinput.getTargetRanges() and the inputType
2988 // is not a simple deletion like replacing selected content with new
2989 // content, the range should end at the original end boundary of the given
2991 const bool preserveEndBoundary
=
2992 (mMode
== Mode::DeletePrecedingBRElementOfBlock
||
2993 mMode
== Mode::DeletePrecedingPreformattedLineBreak
) &&
2994 aComputeRangeFor
== ComputeRangeFor::GetTargetRanges
&&
2995 !MayEditActionDeleteAroundCollapsedSelection(aHTMLEditor
.GetEditAction());
2997 if (mMode
!= Mode::DeletePrecedingPreformattedLineBreak
) {
2998 Element
* const mostDistantInlineAncestor
=
2999 HTMLEditUtils::GetMostDistantAncestorEditableEmptyInlineElement(
3000 *mBRElement
, BlockInlineCheck::UseComputedDisplayOutsideStyle
,
3002 if (preserveEndBoundary
) {
3003 // FIXME: If the range ends at end of an inline element, we may need to
3004 // extend the range.
3005 IgnoredErrorResult error
;
3006 aRangeToDelete
.SetStart(EditorRawDOMPoint(mostDistantInlineAncestor
3007 ? mostDistantInlineAncestor
3009 .ToRawRangeBoundary(),
3011 NS_WARNING_ASSERTION(!error
.Failed(), "nsRange::SetStart() failed");
3012 MOZ_ASSERT_IF(!error
.Failed(), !aRangeToDelete
.Collapsed());
3013 return error
.StealNSResult();
3015 IgnoredErrorResult error
;
3016 aRangeToDelete
.SelectNode(
3017 mostDistantInlineAncestor
? *mostDistantInlineAncestor
: *mBRElement
,
3019 NS_WARNING_ASSERTION(!error
.Failed(), "nsRange::SelectNode() failed");
3020 return error
.StealNSResult();
3023 Element
* const mostDistantInlineAncestor
=
3024 mPreformattedLineBreak
.ContainerAs
<Text
>()->TextDataLength() == 1
3025 ? HTMLEditUtils::GetMostDistantAncestorEditableEmptyInlineElement(
3026 *mPreformattedLineBreak
.ContainerAs
<Text
>(),
3027 BlockInlineCheck::UseComputedDisplayOutsideStyle
, &aEditingHost
)
3030 if (!mostDistantInlineAncestor
) {
3031 if (preserveEndBoundary
) {
3032 // FIXME: If the range ends at end of an inline element, we may need to
3033 // extend the range.
3034 IgnoredErrorResult error
;
3035 aRangeToDelete
.SetStart(mPreformattedLineBreak
.ToRawRangeBoundary(),
3037 MOZ_ASSERT_IF(!error
.Failed(), !aRangeToDelete
.Collapsed());
3038 NS_WARNING_ASSERTION(!error
.Failed(), "nsRange::SetStart() failed");
3039 return error
.StealNSResult();
3041 nsresult rv
= aRangeToDelete
.SetStartAndEnd(
3042 mPreformattedLineBreak
.ToRawRangeBoundary(),
3043 mPreformattedLineBreak
.NextPoint().ToRawRangeBoundary());
3044 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
), "nsRange::SetStartAndEnd() failed");
3048 if (preserveEndBoundary
) {
3049 // FIXME: If the range ends at end of an inline element, we may need to
3050 // extend the range.
3051 IgnoredErrorResult error
;
3052 aRangeToDelete
.SetStart(
3053 EditorRawDOMPoint(mostDistantInlineAncestor
).ToRawRangeBoundary(),
3055 MOZ_ASSERT_IF(!error
.Failed(), !aRangeToDelete
.Collapsed());
3056 NS_WARNING_ASSERTION(!error
.Failed(), "nsRange::SetStart() failed");
3057 return error
.StealNSResult();
3060 IgnoredErrorResult error
;
3061 aRangeToDelete
.SelectNode(*mostDistantInlineAncestor
, error
);
3062 NS_WARNING_ASSERTION(!error
.Failed(), "nsRange::SelectNode() failed");
3063 return error
.StealNSResult();
3066 Result
<EditActionResult
, nsresult
> HTMLEditor::AutoDeleteRangesHandler::
3067 AutoBlockElementsJoiner::HandleDeleteLineBreak(
3068 HTMLEditor
& aHTMLEditor
, nsIEditor::EDirection aDirectionAndAmount
,
3069 const EditorDOMPoint
& aCaretPoint
, const Element
& aEditingHost
) {
3070 MOZ_ASSERT(aHTMLEditor
.IsEditActionDataAvailable());
3071 MOZ_ASSERT(mBRElement
|| mPreformattedLineBreak
.IsSet());
3073 // If we're deleting selection (not replacing with new content), we should
3074 // put caret to end of preceding text node if there is. Then, users can type
3075 // text in it like the other browsers.
3076 EditorDOMPoint pointToPutCaret
= [&]() {
3077 // but when we're deleting a preceding line break of current block, we
3078 // should keep the caret position in the current block.
3079 if (mMode
== Mode::DeletePrecedingBRElementOfBlock
||
3080 mMode
== Mode::DeletePrecedingPreformattedLineBreak
) {
3083 if (!MayEditActionDeleteAroundCollapsedSelection(
3084 aHTMLEditor
.GetEditAction())) {
3085 return EditorDOMPoint();
3087 WSRunScanner
scanner(&aEditingHost
, EditorRawDOMPoint(mBRElement
),
3088 BlockInlineCheck::UseComputedDisplayOutsideStyle
);
3089 const WSScanResult maybePreviousText
=
3090 scanner
.ScanPreviousVisibleNodeOrBlockBoundaryFrom(
3091 EditorRawDOMPoint(mBRElement
));
3092 if (maybePreviousText
.IsContentEditable() &&
3093 maybePreviousText
.InVisibleOrCollapsibleCharacters() &&
3094 !HTMLEditor::GetLinkElement(maybePreviousText
.TextPtr())) {
3095 return maybePreviousText
.PointAfterReachedContent
<EditorDOMPoint
>();
3097 const WSScanResult maybeNextText
=
3098 scanner
.ScanInclusiveNextVisibleNodeOrBlockBoundaryFrom(
3099 EditorRawDOMPoint::After(*mBRElement
));
3100 if (maybeNextText
.IsContentEditable() &&
3101 maybeNextText
.InVisibleOrCollapsibleCharacters()) {
3102 return maybeNextText
.PointAtReachedContent
<EditorDOMPoint
>();
3104 return EditorDOMPoint();
3107 RefPtr
<nsRange
> rangeToDelete
=
3108 nsRange::Create(const_cast<Element
*>(&aEditingHost
));
3109 MOZ_ASSERT(rangeToDelete
);
3111 ComputeRangeToDeleteLineBreak(aHTMLEditor
, *rangeToDelete
, aEditingHost
,
3112 ComputeRangeFor::ToDeleteTheRange
);
3113 if (NS_FAILED(rv
)) {
3115 "AutoBlockElementsJoiner::ComputeRangeToDeleteLineBreak() failed");
3118 Result
<EditActionResult
, nsresult
> result
= HandleDeleteNonCollapsedRange(
3119 aHTMLEditor
, aDirectionAndAmount
, nsIEditor::eNoStrip
, *rangeToDelete
,
3120 SelectionWasCollapsed::Yes
, aEditingHost
);
3121 if (MOZ_UNLIKELY(result
.isErr())) {
3123 "AutoBlockElementsJoiner::HandleDeleteNonCollapsedRange() failed");
3127 if (mLeftContent
&& mRightContent
&&
3128 HTMLEditUtils::GetInclusiveAncestorAnyTableElement(*mLeftContent
) !=
3129 HTMLEditUtils::GetInclusiveAncestorAnyTableElement(*mRightContent
)) {
3130 return EditActionResult::HandledResult();
3133 // Put selection at edge of block and we are done.
3134 if (NS_WARN_IF(mMode
== Mode::DeleteBRElement
&& !mLeafContentInOtherBlock
)) {
3135 // XXX This must be odd case. The other block can be empty.
3136 return Err(NS_ERROR_FAILURE
);
3139 if (pointToPutCaret
.IsSet()) {
3140 nsresult rv
= aHTMLEditor
.CollapseSelectionTo(pointToPutCaret
);
3141 if (NS_WARN_IF(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
3142 return Err(NS_ERROR_EDITOR_DESTROYED
);
3144 if (mMode
== Mode::DeleteBRElement
&& NS_SUCCEEDED(rv
)) {
3145 // If we prefer to use style in the previous line, we should forget
3146 // previous styles since the caret position has all styles which we want
3147 // to use with new content.
3148 if (nsIEditor::DirectionIsBackspace(aDirectionAndAmount
)) {
3149 aHTMLEditor
.TopLevelEditSubActionDataRef()
3150 .mCachedPendingStyles
->Clear();
3152 // And we don't want to keep extending a link at ex-end of the previous
3154 if (HTMLEditor::GetLinkElement(pointToPutCaret
.GetContainer())) {
3155 aHTMLEditor
.mPendingStylesToApplyToNewContent
3156 ->ClearLinkAndItsSpecifiedStyle();
3159 NS_WARNING_ASSERTION(
3161 "EditorBase::CollapseSelectionTo() failed, but ignored");
3163 return EditActionResult::HandledResult();
3166 EditorRawDOMPoint newCaretPosition
=
3167 HTMLEditUtils::GetGoodCaretPointFor
<EditorRawDOMPoint
>(
3168 *mLeafContentInOtherBlock
, aDirectionAndAmount
);
3169 if (MOZ_UNLIKELY(!newCaretPosition
.IsSet())) {
3170 NS_WARNING("HTMLEditUtils::GetGoodCaretPointFor() failed");
3171 return Err(NS_ERROR_FAILURE
);
3173 rv
= aHTMLEditor
.CollapseSelectionTo(newCaretPosition
);
3174 if (NS_WARN_IF(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
3175 return Err(NS_ERROR_EDITOR_DESTROYED
);
3177 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
3178 "EditorBase::CollapseSelectionTo() failed, but ignored");
3179 return EditActionResult::HandledResult();
3182 nsresult
HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
3183 ComputeRangeToDeleteAtOtherBlockBoundary(
3184 const HTMLEditor
& aHTMLEditor
,
3185 nsIEditor::EDirection aDirectionAndAmount
,
3186 const EditorDOMPoint
& aCaretPoint
, nsRange
& aRangeToDelete
,
3187 const Element
& aEditingHost
) const {
3188 MOZ_ASSERT(aHTMLEditor
.IsEditActionDataAvailable());
3189 MOZ_ASSERT(aCaretPoint
.IsSetAndValid());
3190 MOZ_ASSERT(mLeftContent
);
3191 MOZ_ASSERT(mRightContent
);
3193 if (HTMLEditUtils::GetInclusiveAncestorAnyTableElement(*mLeftContent
) !=
3194 HTMLEditUtils::GetInclusiveAncestorAnyTableElement(*mRightContent
)) {
3195 if (!mDeleteRangesHandlerConst
.CanFallbackToDeleteRangeWithTransaction(
3197 nsresult rv
= aRangeToDelete
.CollapseTo(aCaretPoint
.ToRawRangeBoundary());
3198 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
), "nsRange::CollapseTo() failed");
3201 nsresult rv
= mDeleteRangesHandlerConst
3202 .FallbackToComputeRangeToDeleteRangeWithTransaction(
3203 aHTMLEditor
, aRangeToDelete
, aEditingHost
);
3204 NS_WARNING_ASSERTION(
3206 "AutoDeleteRangesHandler::"
3207 "FallbackToComputeRangeToDeleteRangeWithTransaction() failed");
3211 AutoInclusiveAncestorBlockElementsJoiner
joiner(*mLeftContent
,
3213 Result
<bool, nsresult
> canJoinThem
=
3214 joiner
.Prepare(aHTMLEditor
, aEditingHost
);
3215 if (canJoinThem
.isErr()) {
3216 NS_WARNING("AutoInclusiveAncestorBlockElementsJoiner::Prepare() failed");
3217 return canJoinThem
.unwrapErr();
3219 if (canJoinThem
.inspect() && joiner
.CanJoinBlocks() &&
3220 !joiner
.ShouldDeleteLeafContentInstead()) {
3222 joiner
.ComputeRangeToDelete(aHTMLEditor
, aCaretPoint
, aRangeToDelete
);
3223 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
3224 "AutoInclusiveAncestorBlockElementsJoiner::"
3225 "ComputeRangeToDelete() failed");
3229 // If AutoInclusiveAncestorBlockElementsJoiner didn't handle it and it's not
3230 // canceled, user may want to modify the start leaf node or the last leaf
3231 // node of the block.
3232 if (mLeafContentInOtherBlock
== aCaretPoint
.GetContainer()) {
3236 AutoHideSelectionChanges
hideSelectionChanges(aHTMLEditor
.SelectionRef());
3238 // If it's ignored, it didn't modify the DOM tree. In this case, user must
3239 // want to delete nearest leaf node in the other block element.
3240 // TODO: We need to consider this before calling ComputeRangesToDelete() for
3241 // computing the deleting range.
3242 EditorRawDOMPoint newCaretPoint
=
3243 aDirectionAndAmount
== nsIEditor::ePrevious
3244 ? EditorRawDOMPoint::AtEndOf(*mLeafContentInOtherBlock
)
3245 : EditorRawDOMPoint(mLeafContentInOtherBlock
, 0);
3246 // If new caret position is same as current caret position, we can do
3248 if (aRangeToDelete
.Collapsed() &&
3249 aRangeToDelete
.EndRef() == newCaretPoint
.ToRawRangeBoundary()) {
3252 // TODO: Stop modifying the `Selection` for computing the target ranges.
3253 nsresult rv
= aHTMLEditor
.CollapseSelectionTo(newCaretPoint
);
3254 if (MOZ_UNLIKELY(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
3256 "EditorBase::CollapseSelectionTo() caused destroying the editor");
3257 return NS_ERROR_EDITOR_DESTROYED
;
3259 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
3260 "EditorBase::CollapseSelectionTo() failed");
3261 if (NS_SUCCEEDED(rv
)) {
3262 AutoClonedSelectionRangeArray
rangeArray(aHTMLEditor
.SelectionRef());
3263 if (!rangeArray
.GetAncestorLimiter()) {
3264 rangeArray
.SetAncestorLimiter(
3265 aHTMLEditor
.FindSelectionRoot(aEditingHost
));
3267 AutoDeleteRangesHandler
anotherHandler(mDeleteRangesHandlerConst
);
3268 rv
= anotherHandler
.ComputeRangesToDelete(aHTMLEditor
, aDirectionAndAmount
,
3269 rangeArray
, aEditingHost
);
3270 if (NS_SUCCEEDED(rv
)) {
3271 if (MOZ_LIKELY(!rangeArray
.Ranges().IsEmpty())) {
3272 MOZ_ASSERT(rangeArray
.Ranges().Length() == 1);
3273 aRangeToDelete
.SetStartAndEnd(rangeArray
.FirstRangeRef()->StartRef(),
3274 rangeArray
.FirstRangeRef()->EndRef());
3277 "Recursive AutoDeleteRangesHandler::ComputeRangesToDelete() "
3278 "returned no range");
3279 rv
= NS_ERROR_FAILURE
;
3283 "Recursive AutoDeleteRangesHandler::ComputeRangesToDelete() failed");
3286 // Restore selection.
3287 nsresult rvCollapsingSelectionTo
=
3288 aHTMLEditor
.CollapseSelectionTo(aCaretPoint
);
3289 if (MOZ_UNLIKELY(rvCollapsingSelectionTo
== NS_ERROR_EDITOR_DESTROYED
)) {
3291 "EditorBase::CollapseSelectionTo() caused destroying the editor");
3292 return NS_ERROR_EDITOR_DESTROYED
;
3294 NS_WARNING_ASSERTION(
3295 NS_SUCCEEDED(rvCollapsingSelectionTo
),
3296 "EditorBase::CollapseSelectionTo() failed to restore caret position");
3297 return NS_SUCCEEDED(rv
) && NS_SUCCEEDED(rvCollapsingSelectionTo
)
3302 Result
<EditActionResult
, nsresult
> HTMLEditor::AutoDeleteRangesHandler::
3303 AutoBlockElementsJoiner::HandleDeleteAtOtherBlockBoundary(
3304 HTMLEditor
& aHTMLEditor
, nsIEditor::EDirection aDirectionAndAmount
,
3305 nsIEditor::EStripWrappers aStripWrappers
,
3306 const EditorDOMPoint
& aCaretPoint
, nsRange
& aRangeToDelete
,
3307 const Element
& aEditingHost
) {
3308 MOZ_ASSERT(aHTMLEditor
.IsEditActionDataAvailable());
3309 MOZ_ASSERT(aCaretPoint
.IsSetAndValid());
3310 MOZ_ASSERT(mDeleteRangesHandler
);
3311 MOZ_ASSERT(mLeftContent
);
3312 MOZ_ASSERT(mRightContent
);
3314 if (HTMLEditUtils::GetInclusiveAncestorAnyTableElement(*mLeftContent
) !=
3315 HTMLEditUtils::GetInclusiveAncestorAnyTableElement(*mRightContent
)) {
3316 // If we have not deleted `<br>` element and are not called recursively,
3317 // we should call `DeleteRangesWithTransaction()` here.
3318 if (!mDeleteRangesHandler
->CanFallbackToDeleteRangeWithTransaction(
3320 return EditActionResult::IgnoredResult();
3322 Result
<CaretPoint
, nsresult
> caretPointOrError
=
3323 mDeleteRangesHandler
->FallbackToDeleteRangeWithTransaction(
3324 aHTMLEditor
, aRangeToDelete
);
3325 if (MOZ_UNLIKELY(caretPointOrError
.isErr())) {
3327 "AutoDeleteRangesHandler::FallbackToDeleteRangesWithTransaction() "
3329 return caretPointOrError
.propagateErr();
3331 nsresult rv
= caretPointOrError
.inspect().SuggestCaretPointTo(
3332 aHTMLEditor
, {SuggestCaret::OnlyIfHasSuggestion
,
3333 SuggestCaret::OnlyIfTransactionsAllowedToDoIt
,
3334 SuggestCaret::AndIgnoreTrivialError
});
3335 if (NS_FAILED(rv
)) {
3336 NS_WARNING("CaretPoint::SuggestCaretPointTo() failed");
3339 NS_WARNING_ASSERTION(
3340 rv
!= NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR
,
3341 "CaretPoint::SuggestCaretPointTo() failed, but ignored");
3342 // Don't return "ignored" to avoid to fall it back to delete ranges
3344 return EditActionResult::HandledResult();
3347 // Else we are joining content to block
3348 AutoInclusiveAncestorBlockElementsJoiner
joiner(*mLeftContent
,
3350 Result
<bool, nsresult
> canJoinThem
=
3351 joiner
.Prepare(aHTMLEditor
, aEditingHost
);
3352 if (MOZ_UNLIKELY(canJoinThem
.isErr())) {
3353 NS_WARNING("AutoInclusiveAncestorBlockElementsJoiner::Prepare() failed");
3354 return canJoinThem
.propagateErr();
3357 if (!canJoinThem
.inspect() || !joiner
.CanJoinBlocks()) {
3358 nsresult rv
= aHTMLEditor
.CollapseSelectionTo(aCaretPoint
);
3359 if (NS_WARN_IF(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
3360 return Err(NS_ERROR_EDITOR_DESTROYED
);
3362 NS_WARNING_ASSERTION(
3364 "EditorBase::CollapseSelectionTo() failed, but ignored");
3365 return !canJoinThem
.inspect() ? EditActionResult::CanceledResult()
3366 : EditActionResult::IgnoredResult();
3369 EditorDOMPoint
pointToPutCaret(aCaretPoint
);
3370 AutoTrackDOMPoint
trackPointToPutCaret(aHTMLEditor
.RangeUpdaterRef(),
3372 Result
<DeleteRangeResult
, nsresult
> moveFirstLineResult
=
3373 joiner
.Run(aHTMLEditor
, aEditingHost
);
3374 if (MOZ_UNLIKELY(moveFirstLineResult
.isErr())) {
3375 NS_WARNING("AutoInclusiveAncestorBlockElementsJoiner::Run() failed");
3376 return moveFirstLineResult
.propagateErr();
3378 DeleteRangeResult unwrappedMoveFirstLineResult
= moveFirstLineResult
.unwrap();
3380 if (joiner
.ShouldDeleteLeafContentInstead()) {
3381 NS_ASSERTION(unwrappedMoveFirstLineResult
.Ignored(),
3382 "Assumed `AutoInclusiveAncestorBlockElementsJoiner::Run()` "
3383 "returning ignored, but returned not ignored");
3385 NS_ASSERTION(!unwrappedMoveFirstLineResult
.Ignored(),
3386 "Assumed `AutoInclusiveAncestorBlockElementsJoiner::Run()` "
3387 "returning handled, but returned ignored");
3389 #endif // #ifdef DEBUG
3390 // If we're deleting selection (not replacing with new content) and
3391 // AutoInclusiveAncestorBlockElementsJoiner computed new caret position,
3392 // we should use it. Otherwise, we should keep the our traditional behavior.
3393 if (unwrappedMoveFirstLineResult
.Handled() &&
3394 unwrappedMoveFirstLineResult
.HasCaretPointSuggestion() &&
3395 MayEditActionDeleteAroundCollapsedSelection(
3396 aHTMLEditor
.GetEditAction())) {
3397 EditorDOMPoint pointToPutCaret
=
3398 unwrappedMoveFirstLineResult
.UnwrapCaretPoint();
3399 nsresult rv
= aHTMLEditor
.CollapseSelectionTo(pointToPutCaret
);
3400 if (NS_WARN_IF(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
3401 return Err(NS_ERROR_EDITOR_DESTROYED
);
3403 if (NS_FAILED(rv
)) {
3404 NS_WARNING("EditorBase::CollapseSelectionTo() failed, but ignored");
3405 return EditActionResult::HandledResult();
3407 // If we prefer to use style in the previous line, we should forget
3408 // previous styles since the caret position has all styles which we want
3409 // to use with new content.
3410 if (nsIEditor::DirectionIsBackspace(aDirectionAndAmount
)) {
3411 aHTMLEditor
.TopLevelEditSubActionDataRef().mCachedPendingStyles
->Clear();
3413 // And we don't want to keep extending a link at ex-end of the previous
3415 if (HTMLEditor::GetLinkElement(pointToPutCaret
.GetContainer())) {
3416 aHTMLEditor
.mPendingStylesToApplyToNewContent
3417 ->ClearLinkAndItsSpecifiedStyle();
3419 return EditActionResult::HandledResult();
3421 trackPointToPutCaret
.FlushAndStopTracking();
3422 unwrappedMoveFirstLineResult
.IgnoreCaretPointSuggestion();
3424 // If AutoInclusiveAncestorBlockElementsJoiner didn't handle it and it's not
3425 // canceled, user may want to modify the start leaf node or the last leaf
3426 // node of the block.
3427 if (unwrappedMoveFirstLineResult
.Ignored() &&
3428 mLeafContentInOtherBlock
!= aCaretPoint
.GetContainer()) {
3429 // If it's ignored, it didn't modify the DOM tree. In this case, user
3430 // must want to delete nearest leaf node in the other block element.
3431 // TODO: We need to consider this before calling Run() for computing the
3433 EditorRawDOMPoint newCaretPoint
=
3434 aDirectionAndAmount
== nsIEditor::ePrevious
3435 ? EditorRawDOMPoint::AtEndOf(*mLeafContentInOtherBlock
)
3436 : EditorRawDOMPoint(mLeafContentInOtherBlock
, 0);
3437 // If new caret position is same as current caret position, we can do
3439 if (aRangeToDelete
.Collapsed() &&
3440 aRangeToDelete
.EndRef() == newCaretPoint
.ToRawRangeBoundary()) {
3441 return EditActionResult::CanceledResult();
3443 nsresult rv
= aHTMLEditor
.CollapseSelectionTo(newCaretPoint
);
3444 if (NS_FAILED(rv
)) {
3445 NS_WARNING("EditorBase::CollapseSelectionTo() failed");
3448 AutoClonedSelectionRangeArray
rangesToDelete(aHTMLEditor
.SelectionRef());
3449 if (!rangesToDelete
.GetAncestorLimiter()) {
3450 rangesToDelete
.SetAncestorLimiter(
3451 aHTMLEditor
.FindSelectionRoot(aEditingHost
));
3453 AutoDeleteRangesHandler
anotherHandler(mDeleteRangesHandler
);
3454 Result
<EditActionResult
, nsresult
> fallbackResult
=
3455 anotherHandler
.Run(aHTMLEditor
, aDirectionAndAmount
, aStripWrappers
,
3456 rangesToDelete
, aEditingHost
);
3457 if (MOZ_UNLIKELY(fallbackResult
.isErr())) {
3458 NS_WARNING("Recursive AutoDeleteRangesHandler::Run() failed");
3459 return fallbackResult
;
3461 return fallbackResult
;
3463 // Otherwise, we must have deleted the selection as user expected.
3464 nsresult rv
= aHTMLEditor
.CollapseSelectionTo(pointToPutCaret
);
3465 if (NS_WARN_IF(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
3466 return Err(NS_ERROR_EDITOR_DESTROYED
);
3468 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
3469 "EditorBase::CollapseSelectionTo() failed, but ignored");
3470 return unwrappedMoveFirstLineResult
.Handled()
3471 ? EditActionResult::HandledResult()
3472 : EditActionResult::IgnoredResult();
3475 bool HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
3476 PrepareToDeleteAtCurrentBlockBoundary(
3477 const HTMLEditor
& aHTMLEditor
,
3478 nsIEditor::EDirection aDirectionAndAmount
,
3479 Element
& aCurrentBlockElement
, const EditorDOMPoint
& aCaretPoint
,
3480 const Element
& aEditingHost
) {
3481 MOZ_ASSERT(aHTMLEditor
.IsEditActionDataAvailable());
3483 // At edge of our block. Look beside it and see if we can join to an
3485 mMode
= Mode::JoinCurrentBlock
;
3487 // Don't break the basic structure of the HTML document.
3488 if (aCurrentBlockElement
.IsAnyOfHTMLElements(nsGkAtoms::html
, nsGkAtoms::head
,
3493 // Make sure it's not a table element. If so, cancel the operation
3494 // (translation: users cannot backspace or delete across table cells)
3495 if (HTMLEditUtils::IsAnyTableElement(&aCurrentBlockElement
)) {
3499 auto ScanJoinTarget
= [&]() -> nsIContent
* {
3500 nsIContent
* targetContent
=
3501 aDirectionAndAmount
== nsIEditor::ePrevious
3502 ? HTMLEditUtils::GetPreviousContent(
3503 aCurrentBlockElement
, {WalkTreeOption::IgnoreNonEditableNode
},
3504 BlockInlineCheck::Unused
, &aEditingHost
)
3505 : HTMLEditUtils::GetNextContent(
3506 aCurrentBlockElement
, {WalkTreeOption::IgnoreNonEditableNode
},
3507 BlockInlineCheck::Unused
, &aEditingHost
);
3508 // If found content is an invisible text node, let's scan visible things.
3509 auto IsIgnorableDataNode
= [](nsIContent
* aContent
) {
3510 return aContent
&& HTMLEditUtils::IsRemovableNode(*aContent
) &&
3511 ((aContent
->IsText() &&
3512 aContent
->AsText()->TextIsOnlyWhitespace() &&
3513 !HTMLEditUtils::IsVisibleTextNode(*aContent
->AsText())) ||
3514 (aContent
->IsCharacterData() && !aContent
->IsText()));
3516 if (!IsIgnorableDataNode(targetContent
)) {
3517 return targetContent
;
3519 MOZ_ASSERT(mSkippedInvisibleContents
.IsEmpty());
3520 for (nsIContent
* adjacentContent
=
3521 aDirectionAndAmount
== nsIEditor::ePrevious
3522 ? HTMLEditUtils::GetPreviousContent(
3523 *targetContent
, {WalkTreeOption::StopAtBlockBoundary
},
3524 BlockInlineCheck::UseComputedDisplayOutsideStyle
,
3526 : HTMLEditUtils::GetNextContent(
3527 *targetContent
, {WalkTreeOption::StopAtBlockBoundary
},
3528 BlockInlineCheck::UseComputedDisplayOutsideStyle
,
3532 aDirectionAndAmount
== nsIEditor::ePrevious
3533 ? HTMLEditUtils::GetPreviousContent(
3534 *adjacentContent
, {WalkTreeOption::StopAtBlockBoundary
},
3535 BlockInlineCheck::UseComputedDisplayOutsideStyle
,
3537 : HTMLEditUtils::GetNextContent(
3538 *adjacentContent
, {WalkTreeOption::StopAtBlockBoundary
},
3539 BlockInlineCheck::UseComputedDisplayOutsideStyle
,
3541 // If non-editable element is found, we should not skip it to avoid
3542 // joining too far nodes.
3543 if (!HTMLEditUtils::IsSimplyEditableNode(*adjacentContent
)) {
3546 // If block element is found, we should join last leaf content in it.
3547 if (HTMLEditUtils::IsBlockElement(
3549 BlockInlineCheck::UseComputedDisplayOutsideStyle
)) {
3550 nsIContent
* leafContent
=
3551 aDirectionAndAmount
== nsIEditor::ePrevious
3552 ? HTMLEditUtils::GetLastLeafContent(
3553 *adjacentContent
, {LeafNodeType::OnlyEditableLeafNode
})
3554 : HTMLEditUtils::GetFirstLeafContent(
3555 *adjacentContent
, {LeafNodeType::OnlyEditableLeafNode
});
3556 mSkippedInvisibleContents
.AppendElement(*targetContent
);
3557 return leafContent
? leafContent
: adjacentContent
;
3559 // Only when the found node is an invisible text node or a non-text data
3560 // node, we should keep scanning.
3561 if (IsIgnorableDataNode(adjacentContent
)) {
3562 mSkippedInvisibleContents
.AppendElement(*targetContent
);
3563 targetContent
= adjacentContent
;
3566 // Otherwise, we find a visible things. We should join with last found
3567 // invisible text node.
3570 return targetContent
;
3573 if (aDirectionAndAmount
== nsIEditor::ePrevious
) {
3574 const WSScanResult prevVisibleThing
= [&]() {
3575 // When Backspace at start of a block, we need to delete only a preceding
3576 // <br> element if there is.
3577 const Result
<Element
*, nsresult
>
3578 inclusiveAncestorOfRightChildBlockOrError
= AutoBlockElementsJoiner::
3579 GetMostDistantBlockAncestorIfPointIsStartAtBlock(aCaretPoint
,
3581 if (NS_WARN_IF(inclusiveAncestorOfRightChildBlockOrError
.isErr()) ||
3582 !inclusiveAncestorOfRightChildBlockOrError
.inspect()) {
3583 return WSScanResult::Error();
3585 const WSScanResult prevVisibleThingBeforeCurrentBlock
=
3586 WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary(
3589 inclusiveAncestorOfRightChildBlockOrError
.inspect()),
3590 BlockInlineCheck::UseComputedDisplayOutsideStyle
);
3591 if (!prevVisibleThingBeforeCurrentBlock
.ReachedBRElement() &&
3592 !prevVisibleThingBeforeCurrentBlock
.ReachedPreformattedLineBreak()) {
3593 return WSScanResult::Error();
3595 // There is a preceding line break, but it may be invisible. Then, users
3596 // want to delete its preceding content not only the line break.
3597 // Therefore, let's check whether the line break follows another line
3598 // break or a block boundary. In these cases, the line break causes an
3599 // empty line which users may want to delete.
3600 const auto atPrecedingLineBreak
=
3601 prevVisibleThingBeforeCurrentBlock
3602 .PointAtReachedContent
<EditorRawDOMPoint
>();
3603 MOZ_ASSERT(atPrecedingLineBreak
.IsSet());
3604 const WSScanResult prevVisibleThingBeforeLineBreak
=
3605 WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary(
3606 &aEditingHost
, atPrecedingLineBreak
,
3607 BlockInlineCheck::UseComputedDisplayOutsideStyle
);
3608 if (prevVisibleThingBeforeLineBreak
.ReachedBRElement() ||
3609 prevVisibleThingBeforeLineBreak
.ReachedPreformattedLineBreak() ||
3610 prevVisibleThingBeforeLineBreak
.ReachedCurrentBlockBoundary()) {
3611 // Target the latter line break for things simpler. It's easier to
3612 // compute the target range.
3614 prevVisibleThingBeforeCurrentBlock
.ReachedPreformattedLineBreak() &&
3615 prevVisibleThingBeforeLineBreak
.ReachedPreformattedLineBreak(),
3616 prevVisibleThingBeforeCurrentBlock
3617 .PointAtReachedContent
<EditorRawDOMPoint
>() !=
3618 prevVisibleThingBeforeLineBreak
3619 .PointAtReachedContent
<EditorRawDOMPoint
>());
3620 return prevVisibleThingBeforeCurrentBlock
;
3622 return WSScanResult::Error();
3625 // If previous visible thing is a <br>, we should just delete it without
3626 // unwrapping the first line of the right child block. Note that the <br>
3627 // is always treated as invisible by HTMLEditUtils because it's immediately
3628 // preceding <br> of the block boundary. However, deleting it is fine
3629 // because the above checks whether it causes empty line or not.
3630 if (prevVisibleThing
.ReachedBRElement()) {
3631 mMode
= Mode::DeletePrecedingBRElementOfBlock
;
3632 mBRElement
= prevVisibleThing
.BRElementPtr();
3636 // Same for a preformatted line break.
3637 if (prevVisibleThing
.ReachedPreformattedLineBreak()) {
3638 mMode
= Mode::DeletePrecedingPreformattedLineBreak
;
3639 mPreformattedLineBreak
=
3640 prevVisibleThing
.PointAtReachedContent
<EditorRawDOMPoint
>()
3645 mLeftContent
= ScanJoinTarget();
3646 mRightContent
= aCaretPoint
.GetContainerAs
<nsIContent
>();
3648 mRightContent
= ScanJoinTarget();
3649 mLeftContent
= aCaretPoint
.GetContainerAs
<nsIContent
>();
3653 if (!mLeftContent
|| !mRightContent
) {
3657 // Don't cross table boundaries.
3658 return HTMLEditUtils::GetInclusiveAncestorAnyTableElement(*mLeftContent
) ==
3659 HTMLEditUtils::GetInclusiveAncestorAnyTableElement(*mRightContent
);
3662 nsresult
HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
3663 ComputeRangeToDeleteAtCurrentBlockBoundary(
3664 const HTMLEditor
& aHTMLEditor
, const EditorDOMPoint
& aCaretPoint
,
3665 nsRange
& aRangeToDelete
, const Element
& aEditingHost
) const {
3666 MOZ_ASSERT(mLeftContent
);
3667 MOZ_ASSERT(mRightContent
);
3669 AutoInclusiveAncestorBlockElementsJoiner
joiner(*mLeftContent
,
3671 Result
<bool, nsresult
> canJoinThem
=
3672 joiner
.Prepare(aHTMLEditor
, aEditingHost
);
3673 if (canJoinThem
.isErr()) {
3674 NS_WARNING("AutoInclusiveAncestorBlockElementsJoiner::Prepare() failed");
3675 return canJoinThem
.unwrapErr();
3677 if (canJoinThem
.inspect()) {
3679 joiner
.ComputeRangeToDelete(aHTMLEditor
, aCaretPoint
, aRangeToDelete
);
3680 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
3681 "AutoInclusiveAncestorBlockElementsJoiner::"
3682 "ComputeRangesToDelete() failed");
3686 // In this case, nothing will be deleted so that the affected range should
3688 nsresult rv
= aRangeToDelete
.CollapseTo(aCaretPoint
.ToRawRangeBoundary());
3689 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
), "nsRange::CollapseTo() failed");
3693 Result
<EditActionResult
, nsresult
> HTMLEditor::AutoDeleteRangesHandler::
3694 AutoBlockElementsJoiner::HandleDeleteAtCurrentBlockBoundary(
3695 HTMLEditor
& aHTMLEditor
, nsIEditor::EDirection aDirectionAndAmount
,
3696 const EditorDOMPoint
& aCaretPoint
, const Element
& aEditingHost
) {
3697 MOZ_ASSERT(mLeftContent
);
3698 MOZ_ASSERT(mRightContent
);
3700 AutoInclusiveAncestorBlockElementsJoiner
joiner(*mLeftContent
,
3702 Result
<bool, nsresult
> canJoinThem
=
3703 joiner
.Prepare(aHTMLEditor
, aEditingHost
);
3704 if (MOZ_UNLIKELY(canJoinThem
.isErr())) {
3705 NS_WARNING("AutoInclusiveAncestorBlockElementsJoiner::Prepare() failed");
3706 return Err(canJoinThem
.unwrapErr());
3709 if (!canJoinThem
.inspect() || !joiner
.CanJoinBlocks()) {
3710 nsresult rv
= aHTMLEditor
.CollapseSelectionTo(aCaretPoint
);
3711 if (NS_WARN_IF(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
3712 return Err(NS_ERROR_EDITOR_DESTROYED
);
3714 NS_WARNING_ASSERTION(
3716 "EditorBase::CollapseSelectionTo() failed, but ignored");
3717 return !canJoinThem
.inspect() ? EditActionResult::CanceledResult()
3718 : EditActionResult::HandledResult();
3721 EditorDOMPoint
pointToPutCaret(aCaretPoint
);
3722 AutoTrackDOMPoint
tracker(aHTMLEditor
.RangeUpdaterRef(), &pointToPutCaret
);
3723 Result
<DeleteRangeResult
, nsresult
> moveFirstLineResult
=
3724 joiner
.Run(aHTMLEditor
, aEditingHost
);
3725 if (MOZ_UNLIKELY(moveFirstLineResult
.isErr())) {
3726 NS_WARNING("AutoInclusiveAncestorBlockElementsJoiner::Run() failed");
3727 return moveFirstLineResult
.propagateErr();
3729 DeleteRangeResult unwrappedMoveFirstLineResult
= moveFirstLineResult
.unwrap();
3731 if (joiner
.ShouldDeleteLeafContentInstead()) {
3732 NS_ASSERTION(unwrappedMoveFirstLineResult
.Ignored(),
3733 "Assumed `AutoInclusiveAncestorBlockElementsJoiner::Run()` "
3734 "returning ignored, but returned not ignored");
3736 NS_ASSERTION(!unwrappedMoveFirstLineResult
.Ignored(),
3737 "Assumed `AutoInclusiveAncestorBlockElementsJoiner::Run()` "
3738 "returning handled, but returned ignored");
3740 #endif // #ifdef DEBUG
3742 // Cleaning up invisible nodes which are skipped at scanning mLeftContent or
3745 AutoTrackDOMDeleteRangeResult
trackMoveFirstLineResult(
3746 aHTMLEditor
.RangeUpdaterRef(), &unwrappedMoveFirstLineResult
);
3747 for (const OwningNonNull
<nsIContent
>& content
: mSkippedInvisibleContents
) {
3749 aHTMLEditor
.DeleteNodeWithTransaction(MOZ_KnownLive(content
));
3750 if (NS_FAILED(rv
)) {
3751 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
3755 mSkippedInvisibleContents
.Clear();
3758 // If we're deleting selection (not replacing with new content) and
3759 // AutoInclusiveAncestorBlockElementsJoiner computed new caret position, we
3760 // should use it. Otherwise, we should keep the our traditional behavior.
3761 if (unwrappedMoveFirstLineResult
.Handled() &&
3762 unwrappedMoveFirstLineResult
.HasCaretPointSuggestion() &&
3763 MayEditActionDeleteAroundCollapsedSelection(
3764 aHTMLEditor
.GetEditAction())) {
3765 EditorDOMPoint pointToPutCaret
=
3766 unwrappedMoveFirstLineResult
.UnwrapCaretPoint();
3767 // Don't remove empty inline elements in the plaintext-only mode because
3768 // nobody can restore the style again.
3769 if (pointToPutCaret
.IsSetAndValidInComposedDoc() &&
3770 pointToPutCaret
.IsInContentNode() &&
3771 !aEditingHost
.IsContentEditablePlainTextOnly()) {
3772 AutoTrackDOMPoint
trackCaretPoint(aHTMLEditor
.RangeUpdaterRef(),
3774 Result
<CaretPoint
, nsresult
> caretPointOrError
=
3775 aHTMLEditor
.DeleteEmptyInclusiveAncestorInlineElements(
3776 MOZ_KnownLive(*pointToPutCaret
.ContainerAs
<nsIContent
>()),
3778 if (MOZ_UNLIKELY(caretPointOrError
.isErr())) {
3780 "HTMLEditor::DeleteEmptyInclusiveAncestorInlineElements() failed");
3781 return caretPointOrError
.propagateErr();
3783 trackCaretPoint
.FlushAndStopTracking();
3784 caretPointOrError
.unwrap().MoveCaretPointTo(
3785 pointToPutCaret
, {SuggestCaret::OnlyIfHasSuggestion
});
3787 if ((aHTMLEditor
.IsMailEditor() || aHTMLEditor
.IsPlaintextMailComposer()) &&
3788 MOZ_LIKELY(pointToPutCaret
.IsInContentNode())) {
3789 AutoTrackDOMPoint
trackPointToPutCaret(aHTMLEditor
.RangeUpdaterRef(),
3791 nsresult rv
= aHTMLEditor
.DeleteMostAncestorMailCiteElementIfEmpty(
3792 MOZ_KnownLive(*pointToPutCaret
.ContainerAs
<nsIContent
>()));
3793 if (NS_FAILED(rv
)) {
3795 "HTMLEditor::DeleteMostAncestorMailCiteElementIfEmpty() failed");
3798 trackPointToPutCaret
.FlushAndStopTracking();
3799 if (NS_WARN_IF(!pointToPutCaret
.IsSetAndValidInComposedDoc())) {
3800 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
);
3803 if (aHTMLEditor
.GetTopLevelEditSubAction() ==
3804 EditSubAction::eDeleteSelectedContent
&&
3805 pointToPutCaret
.IsSetAndValidInComposedDoc()) {
3806 AutoTrackDOMPoint
trackCaretPoint(aHTMLEditor
.RangeUpdaterRef(),
3808 Result
<CreateLineBreakResult
, nsresult
> insertPaddingBRElementOrError
=
3809 aHTMLEditor
.InsertPaddingBRElementIfNeeded(
3811 aEditingHost
.IsContentEditablePlainTextOnly()
3812 ? nsIEditor::eNoStrip
3813 : nsIEditor::eStrip
,
3815 if (MOZ_UNLIKELY(insertPaddingBRElementOrError
.isErr())) {
3816 NS_WARNING("HTMLEditor::InsertPaddingBRElementIfNeeded() failed");
3817 return insertPaddingBRElementOrError
.propagateErr();
3819 insertPaddingBRElementOrError
.unwrap().MoveCaretPointTo(
3820 pointToPutCaret
, {SuggestCaret::OnlyIfHasSuggestion
});
3822 nsresult rv
= aHTMLEditor
.CollapseSelectionTo(pointToPutCaret
);
3823 if (NS_WARN_IF(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
3824 return Err(NS_ERROR_EDITOR_DESTROYED
);
3826 if (NS_FAILED(rv
)) {
3827 NS_WARNING("EditorBase::CollapseSelectionTo() failed, but ignored");
3828 return EditActionResult::HandledResult();
3830 // If we prefer to use style in the previous line, we should forget
3831 // previous styles since the caret position has all styles which we want
3832 // to use with new content.
3833 if (nsIEditor::DirectionIsBackspace(aDirectionAndAmount
)) {
3834 aHTMLEditor
.TopLevelEditSubActionDataRef().mCachedPendingStyles
->Clear();
3836 // And we don't want to keep extending a link at ex-end of the previous
3838 if (HTMLEditor::GetLinkElement(pointToPutCaret
.GetContainer())) {
3839 aHTMLEditor
.mPendingStylesToApplyToNewContent
3840 ->ClearLinkAndItsSpecifiedStyle();
3842 return EditActionResult::HandledResult();
3844 unwrappedMoveFirstLineResult
.IgnoreCaretPointSuggestion();
3845 nsresult rv
= aHTMLEditor
.CollapseSelectionTo(pointToPutCaret
);
3846 if (NS_WARN_IF(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
3847 return Err(NS_ERROR_EDITOR_DESTROYED
);
3849 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
3850 "EditorBase::CollapseSelectionTo() failed, but ignored");
3851 // This should claim that trying to join the block means that
3852 // this handles the action because the caller shouldn't do anything
3853 // anymore in this case.
3854 return EditActionResult::HandledResult();
3858 HTMLEditor::AutoDeleteRangesHandler::ComputeRangesToDeleteNonCollapsedRanges(
3859 const HTMLEditor
& aHTMLEditor
, nsIEditor::EDirection aDirectionAndAmount
,
3860 AutoClonedSelectionRangeArray
& aRangesToDelete
,
3861 AutoDeleteRangesHandler::SelectionWasCollapsed aSelectionWasCollapsed
,
3862 const Element
& aEditingHost
) const {
3863 MOZ_ASSERT(!aRangesToDelete
.IsCollapsed());
3865 if (NS_WARN_IF(!aRangesToDelete
.FirstRangeRef()->StartRef().IsSet()) ||
3866 NS_WARN_IF(!aRangesToDelete
.FirstRangeRef()->EndRef().IsSet())) {
3867 return NS_ERROR_FAILURE
;
3870 if (aRangesToDelete
.Ranges().Length() == 1) {
3871 Result
<EditorRawDOMRange
, nsresult
> result
= ExtendOrShrinkRangeToDelete(
3872 aHTMLEditor
, aRangesToDelete
.LimitersAndCaretDataRef(),
3873 EditorRawDOMRange(aRangesToDelete
.FirstRangeRef()));
3874 if (MOZ_UNLIKELY(result
.isErr())) {
3876 "AutoDeleteRangesHandler::ExtendOrShrinkRangeToDelete() failed");
3877 return NS_ERROR_FAILURE
;
3879 EditorRawDOMRange
newRange(result
.unwrap());
3880 if (MOZ_UNLIKELY(NS_FAILED(aRangesToDelete
.FirstRangeRef()->SetStartAndEnd(
3881 newRange
.StartRef().ToRawRangeBoundary(),
3882 newRange
.EndRef().ToRawRangeBoundary())))) {
3883 NS_WARNING("nsRange::SetStartAndEnd() failed");
3884 return NS_ERROR_FAILURE
;
3887 NS_WARN_IF(!aRangesToDelete
.FirstRangeRef()->IsPositioned()))) {
3888 return NS_ERROR_FAILURE
;
3890 if (NS_WARN_IF(aRangesToDelete
.FirstRangeRef()->Collapsed())) {
3891 return NS_OK
; // Hmm, there is nothing to delete...?
3895 if (!aHTMLEditor
.IsPlaintextMailComposer()) {
3896 EditorDOMRange
firstRange(aRangesToDelete
.FirstRangeRef());
3897 EditorDOMRange extendedRange
=
3898 WSRunScanner::GetRangeContainingInvisibleWhiteSpacesAtRangeBoundaries(
3899 EditorDOMRange(aRangesToDelete
.FirstRangeRef()));
3900 if (firstRange
!= extendedRange
) {
3901 nsresult rv
= aRangesToDelete
.FirstRangeRef()->SetStartAndEnd(
3902 extendedRange
.StartRef().ToRawRangeBoundary(),
3903 extendedRange
.EndRef().ToRawRangeBoundary());
3904 if (NS_FAILED(rv
)) {
3905 NS_WARNING("nsRange::SetStartAndEnd() failed");
3906 return NS_ERROR_FAILURE
;
3911 if (aRangesToDelete
.FirstRangeRef()->GetStartContainer() ==
3912 aRangesToDelete
.FirstRangeRef()->GetEndContainer()) {
3913 if (!aRangesToDelete
.FirstRangeRef()->Collapsed()) {
3914 nsresult rv
= ComputeRangesToDeleteRangesWithTransaction(
3915 aHTMLEditor
, aDirectionAndAmount
, aRangesToDelete
, aEditingHost
);
3916 NS_WARNING_ASSERTION(
3918 "AutoDeleteRangesHandler::ComputeRangesToDeleteRangesWithTransaction("
3922 // `DeleteUnnecessaryNodes()` may delete parent elements, but it does not
3923 // affect computing target ranges. Therefore, we don't need to touch
3924 // aRangesToDelete in this case.
3928 Element
* startCiteNode
= aHTMLEditor
.GetMostDistantAncestorMailCiteElement(
3929 *aRangesToDelete
.FirstRangeRef()->GetStartContainer());
3930 Element
* endCiteNode
= aHTMLEditor
.GetMostDistantAncestorMailCiteElement(
3931 *aRangesToDelete
.FirstRangeRef()->GetEndContainer());
3933 if (startCiteNode
&& !endCiteNode
) {
3934 aDirectionAndAmount
= nsIEditor::eNext
;
3935 } else if (!startCiteNode
&& endCiteNode
) {
3936 aDirectionAndAmount
= nsIEditor::ePrevious
;
3939 for (const OwningNonNull
<nsRange
>& range
: aRangesToDelete
.Ranges()) {
3940 if (MOZ_UNLIKELY(range
->Collapsed())) {
3943 AutoBlockElementsJoiner
joiner(*this);
3944 if (!joiner
.PrepareToDeleteNonCollapsedRange(aHTMLEditor
, range
,
3946 return NS_ERROR_FAILURE
;
3948 nsresult rv
= joiner
.ComputeRangeToDelete(
3949 aHTMLEditor
, aRangesToDelete
, aDirectionAndAmount
, range
,
3950 aSelectionWasCollapsed
, aEditingHost
);
3951 if (NS_FAILED(rv
)) {
3952 NS_WARNING("AutoBlockElementsJoiner::ComputeRangeToDelete() failed");
3959 Result
<EditActionResult
, nsresult
>
3960 HTMLEditor::AutoDeleteRangesHandler::HandleDeleteNonCollapsedRanges(
3961 HTMLEditor
& aHTMLEditor
, nsIEditor::EDirection aDirectionAndAmount
,
3962 nsIEditor::EStripWrappers aStripWrappers
,
3963 AutoClonedSelectionRangeArray
& aRangesToDelete
,
3964 SelectionWasCollapsed aSelectionWasCollapsed
, const Element
& aEditingHost
) {
3965 MOZ_ASSERT(aHTMLEditor
.IsTopLevelEditSubActionDataAvailable());
3966 MOZ_ASSERT(!aRangesToDelete
.IsCollapsed());
3968 if (NS_WARN_IF(!aRangesToDelete
.FirstRangeRef()->StartRef().IsSet()) ||
3969 NS_WARN_IF(!aRangesToDelete
.FirstRangeRef()->EndRef().IsSet())) {
3970 return Err(NS_ERROR_FAILURE
);
3973 MOZ_ASSERT_IF(aRangesToDelete
.Ranges().Length() == 1,
3974 aRangesToDelete
.IsFirstRangeEditable(aEditingHost
));
3976 // Else we have a non-collapsed selection. First adjust the selection.
3977 // XXX Why do we extend selection only when there is only one range?
3978 if (aRangesToDelete
.Ranges().Length() == 1) {
3979 Result
<EditorRawDOMRange
, nsresult
> result
= ExtendOrShrinkRangeToDelete(
3980 aHTMLEditor
, aRangesToDelete
.LimitersAndCaretDataRef(),
3981 EditorRawDOMRange(aRangesToDelete
.FirstRangeRef()));
3982 if (MOZ_UNLIKELY(result
.isErr())) {
3984 "AutoDeleteRangesHandler::ExtendOrShrinkRangeToDelete() failed");
3985 return Err(NS_ERROR_FAILURE
);
3987 EditorRawDOMRange
newRange(result
.unwrap());
3988 if (NS_FAILED(aRangesToDelete
.FirstRangeRef()->SetStartAndEnd(
3989 newRange
.StartRef().ToRawRangeBoundary(),
3990 newRange
.EndRef().ToRawRangeBoundary()))) {
3991 NS_WARNING("nsRange::SetStartAndEnd() failed");
3992 return Err(NS_ERROR_FAILURE
);
3994 if (NS_WARN_IF(!aRangesToDelete
.FirstRangeRef()->IsPositioned())) {
3995 return Err(NS_ERROR_FAILURE
);
3997 if (NS_WARN_IF(aRangesToDelete
.FirstRangeRef()->Collapsed())) {
3998 // Hmm, there is nothing to delete...?
3999 // In this case, the callers want collapsed selection. Therefore, we need
4000 // to change the `Selection` here.
4001 nsresult rv
= aHTMLEditor
.CollapseSelectionTo(
4002 aRangesToDelete
.GetFirstRangeStartPoint
<EditorRawDOMPoint
>());
4003 if (NS_FAILED(rv
)) {
4004 NS_WARNING("EditorBase::CollapseSelectionTo() failed");
4007 return EditActionResult::HandledResult();
4009 MOZ_ASSERT(aRangesToDelete
.IsFirstRangeEditable(aEditingHost
));
4012 // Remember that we did a ranged delete for the benefit of AfterEditInner().
4013 aHTMLEditor
.TopLevelEditSubActionDataRef().mDidDeleteNonCollapsedRange
= true;
4015 // Figure out if the endpoints are in nodes that can be merged. Adjust
4016 // surrounding white-space in preparation to delete selection.
4017 if (!aHTMLEditor
.IsPlaintextMailComposer()) {
4019 AutoTrackDOMRange
firstRangeTracker(aHTMLEditor
.RangeUpdaterRef(),
4020 &aRangesToDelete
.FirstRangeRef());
4021 Result
<CaretPoint
, nsresult
> caretPointOrError
=
4022 WhiteSpaceVisibilityKeeper::PrepareToDeleteRange(
4023 aHTMLEditor
, EditorDOMRange(aRangesToDelete
.FirstRangeRef()),
4025 if (MOZ_UNLIKELY(caretPointOrError
.isErr())) {
4026 NS_WARNING("WhiteSpaceVisibilityKeeper::PrepareToDeleteRange() failed");
4027 return caretPointOrError
.propagateErr();
4029 // Ignore caret point suggestion because there was
4030 // AutoTransactionsConserveSelection.
4031 caretPointOrError
.unwrap().IgnoreCaretPointSuggestion();
4033 if (NS_WARN_IF(!aRangesToDelete
.FirstRangeRef()->IsPositioned()) ||
4034 (aHTMLEditor
.MayHaveMutationEventListeners() &&
4035 NS_WARN_IF(!aRangesToDelete
.IsFirstRangeEditable(aEditingHost
)))) {
4037 "WhiteSpaceVisibilityKeeper::PrepareToDeleteRange() made the first "
4039 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
);
4043 // XXX This is odd. We do we simply use `DeleteRangesWithTransaction()`
4044 // only when **first** range is in same container?
4045 if (aRangesToDelete
.FirstRangeRef()->GetStartContainer() ==
4046 aRangesToDelete
.FirstRangeRef()->GetEndContainer()) {
4047 // Because of previous DOM tree changes, the range may be collapsed.
4048 // If we've already removed all contents in the range, we shouldn't
4049 // delete anything around the caret.
4050 if (!aRangesToDelete
.FirstRangeRef()->Collapsed()) {
4052 AutoTrackDOMRange
firstRangeTracker(aHTMLEditor
.RangeUpdaterRef(),
4053 &aRangesToDelete
.FirstRangeRef());
4054 Result
<CaretPoint
, nsresult
> caretPointOrError
=
4055 aHTMLEditor
.DeleteRangesWithTransaction(
4056 aDirectionAndAmount
, aStripWrappers
, aRangesToDelete
);
4057 if (MOZ_UNLIKELY(caretPointOrError
.isErr())) {
4058 NS_WARNING("HTMLEditor::DeleteRangesWithTransaction() failed");
4059 return caretPointOrError
.propagateErr();
4061 nsresult rv
= caretPointOrError
.inspect().SuggestCaretPointTo(
4062 aHTMLEditor
, {SuggestCaret::OnlyIfHasSuggestion
,
4063 SuggestCaret::OnlyIfTransactionsAllowedToDoIt
,
4064 SuggestCaret::AndIgnoreTrivialError
});
4065 if (NS_FAILED(rv
)) {
4066 NS_WARNING("CaretPoint::SuggestCaretPointTo() failed");
4069 NS_WARNING_ASSERTION(
4070 rv
!= NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR
,
4071 "CaretPoint::SuggestCaretPointTo() failed, but ignored");
4073 if (NS_WARN_IF(!aRangesToDelete
.FirstRangeRef()->IsPositioned()) ||
4074 (aHTMLEditor
.MayHaveMutationEventListeners(
4075 NS_EVENT_BITS_MUTATION_NODEREMOVED
|
4076 NS_EVENT_BITS_MUTATION_NODEREMOVEDFROMDOCUMENT
|
4077 NS_EVENT_BITS_MUTATION_SUBTREEMODIFIED
) &&
4078 NS_WARN_IF(!aRangesToDelete
.IsFirstRangeEditable(aEditingHost
)))) {
4080 "HTMLEditor::DeleteRangesWithTransaction() made the first range "
4082 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
);
4085 // However, even if the range is removed, we may need to clean up the
4086 // containers which become empty.
4087 EditorDOMRange
rangeToCleanUp(aRangesToDelete
.FirstRangeRef());
4088 AutoTrackDOMRange
trackRangeToCleanUp(aHTMLEditor
.RangeUpdaterRef(),
4091 DeleteUnnecessaryNodes(aHTMLEditor
, rangeToCleanUp
, aEditingHost
);
4092 if (NS_FAILED(rv
)) {
4093 NS_WARNING("AutoDeleteRangesHandler::DeleteUnnecessaryNodes() failed");
4096 trackRangeToCleanUp
.FlushAndStopTracking();
4097 if (NS_WARN_IF(!rangeToCleanUp
.IsPositionedAndValidInComposedDoc())) {
4098 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
);
4100 const auto& pointToPutCaret
=
4101 !nsIEditor::DirectionIsBackspace(aDirectionAndAmount
) ||
4102 (aHTMLEditor
.TopLevelEditSubActionDataRef()
4103 .mDidDeleteEmptyParentBlocks
&&
4104 (aHTMLEditor
.GetEditAction() == EditAction::eDrop
||
4105 aHTMLEditor
.GetEditAction() == EditAction::eDeleteByDrag
))
4106 ? rangeToCleanUp
.StartRef()
4107 : rangeToCleanUp
.EndRef();
4108 rv
= aHTMLEditor
.CollapseSelectionTo(pointToPutCaret
);
4109 if (NS_FAILED(rv
)) {
4110 NS_WARNING("EditorBase::CollapseSelectionTo() failed");
4113 return EditActionResult::HandledResult();
4117 !aRangesToDelete
.FirstRangeRef()->GetStartContainer()->IsContent()) ||
4119 !aRangesToDelete
.FirstRangeRef()->GetEndContainer()->IsContent())) {
4120 return Err(NS_ERROR_FAILURE
);
4123 // Figure out mailcite ancestors
4124 RefPtr
<Element
> startCiteNode
=
4125 aHTMLEditor
.GetMostDistantAncestorMailCiteElement(
4126 *aRangesToDelete
.FirstRangeRef()->GetStartContainer());
4127 RefPtr
<Element
> endCiteNode
=
4128 aHTMLEditor
.GetMostDistantAncestorMailCiteElement(
4129 *aRangesToDelete
.FirstRangeRef()->GetEndContainer());
4131 // If we only have a mailcite at one of the two endpoints, set the
4132 // directionality of the deletion so that the selection will end up
4133 // outside the mailcite.
4134 if (startCiteNode
&& !endCiteNode
) {
4135 aDirectionAndAmount
= nsIEditor::eNext
;
4136 } else if (!startCiteNode
&& endCiteNode
) {
4137 aDirectionAndAmount
= nsIEditor::ePrevious
;
4140 MOZ_ASSERT(!aRangesToDelete
.Ranges().IsEmpty());
4141 auto ret
= EditActionResult::IgnoredResult();
4142 for (const OwningNonNull
<nsRange
>& range
: aRangesToDelete
.Ranges()) {
4143 if (MOZ_UNLIKELY(range
->Collapsed())) {
4146 AutoBlockElementsJoiner
joiner(*this);
4147 if (!joiner
.PrepareToDeleteNonCollapsedRange(aHTMLEditor
, range
,
4149 return Err(NS_ERROR_FAILURE
);
4151 Result
<EditActionResult
, nsresult
> result
=
4152 joiner
.Run(aHTMLEditor
, aRangesToDelete
.LimitersAndCaretDataRef(),
4153 aDirectionAndAmount
, aStripWrappers
, MOZ_KnownLive(range
),
4154 aSelectionWasCollapsed
, aEditingHost
);
4155 if (MOZ_UNLIKELY(result
.isErr())) {
4156 NS_WARNING("AutoBlockElementsJoiner::Run() failed");
4159 ret
|= result
.inspect();
4164 bool HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
4165 PrepareToDeleteNonCollapsedRange(const HTMLEditor
& aHTMLEditor
,
4166 const nsRange
& aRangeToDelete
,
4167 const Element
& aEditingHost
) {
4168 MOZ_ASSERT(aHTMLEditor
.IsEditActionDataAvailable());
4169 MOZ_ASSERT(!aRangeToDelete
.Collapsed());
4171 mLeftContent
= HTMLEditUtils::GetInclusiveAncestorElement(
4172 *aRangeToDelete
.GetStartContainer()->AsContent(),
4173 HTMLEditUtils::ClosestEditableBlockElement
,
4174 BlockInlineCheck::UseComputedDisplayOutsideStyle
);
4175 mRightContent
= HTMLEditUtils::GetInclusiveAncestorElement(
4176 *aRangeToDelete
.GetEndContainer()->AsContent(),
4177 HTMLEditUtils::ClosestEditableBlockElement
,
4178 BlockInlineCheck::UseComputedDisplayOutsideStyle
);
4179 // Note that mLeftContent and/or mRightContent can be nullptr if editing host
4180 // is an inline element. If both editable ancestor block is exactly same
4181 // one or one reaches an inline editing host, we can just delete the content
4183 if (mLeftContent
== mRightContent
|| !mLeftContent
|| !mRightContent
) {
4185 !mLeftContent
|| !mRightContent
,
4186 aRangeToDelete
.GetStartContainer()->AsContent()->GetEditingHost() ==
4187 aRangeToDelete
.GetEndContainer()->AsContent()->GetEditingHost());
4188 mMode
= Mode::DeleteContentInRange
;
4192 // If left block and right block are adjacent siblings and they are same
4193 // type of elements, we can merge them after deleting the selected contents.
4194 // MOOSE: this could conceivably screw up a table.. fix me.
4195 if (mLeftContent
->GetParentNode() == mRightContent
->GetParentNode() &&
4196 HTMLEditUtils::CanContentsBeJoined(*mLeftContent
, *mRightContent
) &&
4197 // XXX What's special about these three types of block?
4198 (mLeftContent
->IsHTMLElement(nsGkAtoms::p
) ||
4199 HTMLEditUtils::IsListItem(mLeftContent
) ||
4200 HTMLEditUtils::IsHeader(*mLeftContent
))) {
4201 mMode
= Mode::JoinBlocksInSameParent
;
4205 // If the range starts immediately after a line end and ends in a
4206 // child right block, we should not unwrap the right block unless the
4207 // right block will have no nodes.
4208 if (mRightContent
->IsInclusiveDescendantOf(mLeftContent
)) {
4209 // FYI: Chrome does not remove the right child block even if there will be
4210 // only single <br> or a comment node in it. Therefore, we should use this
4212 const WSScanResult nextVisibleThingOfEndBoundary
=
4213 WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary(
4214 &aEditingHost
, EditorRawDOMPoint(aRangeToDelete
.EndRef()),
4215 BlockInlineCheck::UseComputedDisplayOutsideStyle
);
4216 if (!nextVisibleThingOfEndBoundary
.ReachedCurrentBlockBoundary()) {
4217 MOZ_ASSERT(mLeftContent
->IsElement());
4218 Result
<Element
*, nsresult
> mostDistantBlockOrError
=
4219 AutoBlockElementsJoiner::
4220 GetMostDistantBlockAncestorIfPointIsStartAtBlock(
4221 EditorRawDOMPoint(mRightContent
, 0), aEditingHost
,
4222 mLeftContent
->AsElement());
4223 MOZ_ASSERT(mostDistantBlockOrError
.isOk());
4224 if (MOZ_LIKELY(mostDistantBlockOrError
.inspect())) {
4225 const WSScanResult prevVisibleThingOfStartBoundary
=
4226 WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary(
4227 &aEditingHost
, EditorRawDOMPoint(aRangeToDelete
.StartRef()),
4228 BlockInlineCheck::UseComputedDisplayOutsideStyle
);
4229 if (prevVisibleThingOfStartBoundary
.ReachedBRElement()) {
4230 // If the range start after a <br> followed by the block boundary,
4231 // we want to delete the <br> or following <br> element unless it's
4232 // not a part of empty line like `<div>abc<br>{<div>]def`.
4233 const WSScanResult nextVisibleThingOfBR
=
4234 WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary(
4236 EditorRawDOMPoint::After(
4237 *prevVisibleThingOfStartBoundary
.GetContent()),
4238 BlockInlineCheck::UseComputedDisplayOutsideStyle
);
4239 MOZ_ASSERT(!nextVisibleThingOfBR
.ReachedCurrentBlockBoundary());
4240 if (!nextVisibleThingOfBR
.ReachedOtherBlockElement() ||
4241 nextVisibleThingOfBR
.GetContent() !=
4242 mostDistantBlockOrError
.inspect()) {
4243 // The range selects a non-empty line or a child block at least.
4244 mMode
= Mode::DeletePrecedingLinesAndContentInRange
;
4247 const WSScanResult prevVisibleThingOfBR
=
4248 WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary(
4251 prevVisibleThingOfStartBoundary
.GetContent()),
4252 BlockInlineCheck::UseComputedDisplayOutsideStyle
);
4253 if (prevVisibleThingOfBR
.ReachedBRElement() ||
4254 prevVisibleThingOfBR
.ReachedPreformattedLineBreak() ||
4255 prevVisibleThingOfBR
.ReachedBlockBoundary()) {
4256 // The preceding <br> causes an empty line.
4257 mMode
= Mode::DeletePrecedingLinesAndContentInRange
;
4260 } else if (prevVisibleThingOfStartBoundary
4261 .ReachedPreformattedLineBreak()) {
4262 const WSScanResult nextVisibleThingOfLineBreak
=
4263 WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary(
4265 prevVisibleThingOfStartBoundary
4266 .PointAfterReachedContent
<EditorRawDOMPoint
>(),
4267 BlockInlineCheck::UseComputedDisplayOutsideStyle
);
4269 !nextVisibleThingOfLineBreak
.ReachedCurrentBlockBoundary());
4270 if (!nextVisibleThingOfLineBreak
.ReachedOtherBlockElement() ||
4271 nextVisibleThingOfLineBreak
.GetContent() !=
4272 mostDistantBlockOrError
.inspect()) {
4273 // The range selects a non-empty line or a child block at least.
4274 mMode
= Mode::DeletePrecedingLinesAndContentInRange
;
4277 const WSScanResult prevVisibleThingOfLineBreak
=
4278 WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary(
4280 prevVisibleThingOfStartBoundary
4281 .PointAtReachedContent
<EditorRawDOMPoint
>(),
4282 BlockInlineCheck::UseComputedDisplayOutsideStyle
);
4283 if (prevVisibleThingOfLineBreak
.ReachedBRElement() ||
4284 prevVisibleThingOfLineBreak
.ReachedPreformattedLineBreak() ||
4285 prevVisibleThingOfLineBreak
.ReachedBlockBoundary()) {
4286 // The preceding line break causes an empty line.
4287 mMode
= Mode::DeletePrecedingLinesAndContentInRange
;
4290 } else if (prevVisibleThingOfStartBoundary
4291 .ReachedCurrentBlockBoundary()) {
4292 MOZ_ASSERT(prevVisibleThingOfStartBoundary
.ElementPtr() ==
4294 const WSScanResult firstVisibleThingInBlock
=
4295 WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary(
4298 prevVisibleThingOfStartBoundary
.ElementPtr(), 0),
4299 BlockInlineCheck::UseComputedDisplayOutsideStyle
);
4300 if (!firstVisibleThingInBlock
.ReachedOtherBlockElement() ||
4301 firstVisibleThingInBlock
.ElementPtr() !=
4302 mostDistantBlockOrError
.inspect()) {
4303 mMode
= Mode::DeletePrecedingLinesAndContentInRange
;
4306 } else if (prevVisibleThingOfStartBoundary
.ReachedOtherBlockElement()) {
4307 const WSScanResult firstVisibleThingAfterBlock
=
4308 WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary(
4310 EditorRawDOMPoint::After(
4311 *prevVisibleThingOfStartBoundary
.ElementPtr()),
4312 BlockInlineCheck::UseComputedDisplayOutsideStyle
);
4313 if (!firstVisibleThingAfterBlock
.ReachedOtherBlockElement() ||
4314 firstVisibleThingAfterBlock
.ElementPtr() !=
4315 mostDistantBlockOrError
.inspect()) {
4316 mMode
= Mode::DeletePrecedingLinesAndContentInRange
;
4324 mMode
= Mode::DeleteNonCollapsedRange
;
4328 nsresult
HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
4329 ComputeRangeToDeleteContentInRange(
4330 const HTMLEditor
& aHTMLEditor
,
4331 nsIEditor::EDirection aDirectionAndAmount
, nsRange
& aRangeToDelete
,
4332 const Element
& aEditingHost
) const {
4333 MOZ_ASSERT(aHTMLEditor
.IsEditActionDataAvailable());
4334 MOZ_ASSERT(!aRangeToDelete
.Collapsed());
4335 MOZ_ASSERT(mMode
== Mode::DeleteContentInRange
);
4336 MOZ_ASSERT(aRangeToDelete
.GetStartContainer()->AsContent()->GetEditingHost());
4338 aRangeToDelete
.GetStartContainer()->AsContent()->GetEditingHost() ==
4339 aRangeToDelete
.GetEndContainer()->AsContent()->GetEditingHost());
4340 MOZ_ASSERT(!mLeftContent
== !mRightContent
);
4341 MOZ_ASSERT_IF(mLeftContent
, mLeftContent
->IsElement());
4342 MOZ_ASSERT_IF(mLeftContent
,
4343 aRangeToDelete
.GetStartContainer()->IsInclusiveDescendantOf(
4345 MOZ_ASSERT_IF(mRightContent
, mRightContent
->IsElement());
4348 aRangeToDelete
.GetEndContainer()->IsInclusiveDescendantOf(mRightContent
));
4351 HTMLEditUtils::IsInlineContent(
4352 *aRangeToDelete
.GetStartContainer()->AsContent()->GetEditingHost(),
4353 BlockInlineCheck::UseComputedDisplayOutsideStyle
));
4356 mDeleteRangesHandlerConst
.ComputeRangeToDeleteRangeWithTransaction(
4357 aHTMLEditor
, aDirectionAndAmount
, aRangeToDelete
, aEditingHost
);
4358 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
4359 "AutoDeleteRangesHandler::"
4360 "ComputeRangeToDeleteRangeWithTransaction() failed");
4364 Result
<EditActionResult
, nsresult
> HTMLEditor::AutoDeleteRangesHandler::
4365 AutoBlockElementsJoiner::DeleteContentInRange(
4366 HTMLEditor
& aHTMLEditor
,
4367 const LimitersAndCaretData
& aLimitersAndCaretData
,
4368 nsIEditor::EDirection aDirectionAndAmount
,
4369 nsIEditor::EStripWrappers aStripWrappers
, nsRange
& aRangeToDelete
,
4370 const Element
& aEditingHost
) {
4371 MOZ_ASSERT(aHTMLEditor
.IsEditActionDataAvailable());
4372 MOZ_ASSERT(!aRangeToDelete
.Collapsed());
4373 MOZ_ASSERT(mMode
== Mode::DeleteContentInRange
);
4374 MOZ_ASSERT(mDeleteRangesHandler
);
4375 MOZ_ASSERT(aRangeToDelete
.GetStartContainer()->AsContent()->GetEditingHost());
4377 aRangeToDelete
.GetStartContainer()->AsContent()->GetEditingHost() ==
4378 aRangeToDelete
.GetEndContainer()->AsContent()->GetEditingHost());
4379 MOZ_ASSERT_IF(mLeftContent
, mLeftContent
->IsElement());
4380 MOZ_ASSERT_IF(mLeftContent
,
4381 aRangeToDelete
.GetStartContainer()->IsInclusiveDescendantOf(
4383 MOZ_ASSERT_IF(mRightContent
, mRightContent
->IsElement());
4386 aRangeToDelete
.GetEndContainer()->IsInclusiveDescendantOf(mRightContent
));
4389 HTMLEditUtils::IsInlineContent(
4390 *aRangeToDelete
.GetStartContainer()->AsContent()->GetEditingHost(),
4391 BlockInlineCheck::UseComputedDisplayOutsideStyle
));
4393 RefPtr
<nsRange
> rangeToDelete(&aRangeToDelete
);
4395 AutoClonedSelectionRangeArray
rangesToDelete(*rangeToDelete
,
4396 aLimitersAndCaretData
);
4397 AutoTrackDOMRange
trackRangeToDelete(aHTMLEditor
.RangeUpdaterRef(),
4399 Result
<CaretPoint
, nsresult
> caretPointOrError
=
4400 aHTMLEditor
.DeleteRangesWithTransaction(aDirectionAndAmount
,
4401 aStripWrappers
, rangesToDelete
);
4402 if (MOZ_UNLIKELY(caretPointOrError
.isErr())) {
4403 if (NS_WARN_IF(caretPointOrError
.inspectErr() ==
4404 NS_ERROR_EDITOR_DESTROYED
)) {
4405 return Err(NS_ERROR_EDITOR_DESTROYED
);
4408 "HTMLEditor::DeleteRangesWithTransaction() failed, but ignored");
4410 nsresult rv
= caretPointOrError
.inspect().SuggestCaretPointTo(
4411 aHTMLEditor
, {SuggestCaret::OnlyIfHasSuggestion
,
4412 SuggestCaret::OnlyIfTransactionsAllowedToDoIt
,
4413 SuggestCaret::AndIgnoreTrivialError
});
4414 if (NS_FAILED(rv
)) {
4415 NS_WARNING("CaretPoint::SuggestCaretPointTo() failed");
4418 NS_WARNING_ASSERTION(
4419 rv
!= NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR
,
4420 "CaretPoint::SuggestCaretPointTo() failed, but ignored");
4424 if (NS_WARN_IF(!rangeToDelete
->IsPositioned())) {
4425 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
);
4428 EditorDOMRange
rangeToCleanUp(*rangeToDelete
);
4429 AutoTrackDOMRange
trackRangeToCleanUp(aHTMLEditor
.RangeUpdaterRef(),
4431 nsresult rv
= mDeleteRangesHandler
->DeleteUnnecessaryNodes(
4432 aHTMLEditor
, rangeToCleanUp
, aEditingHost
);
4433 if (NS_FAILED(rv
)) {
4434 NS_WARNING("AutoDeleteRangesHandler::DeleteUnnecessaryNodes() failed");
4437 const auto& pointToPutCaret
=
4438 !nsIEditor::DirectionIsBackspace(aDirectionAndAmount
) ||
4439 (aHTMLEditor
.TopLevelEditSubActionDataRef()
4440 .mDidDeleteEmptyParentBlocks
&&
4441 (aHTMLEditor
.GetEditAction() == EditAction::eDrop
||
4442 aHTMLEditor
.GetEditAction() == EditAction::eDeleteByDrag
))
4443 ? rangeToCleanUp
.StartRef()
4444 : rangeToCleanUp
.EndRef();
4445 rv
= aHTMLEditor
.CollapseSelectionTo(pointToPutCaret
);
4446 if (NS_FAILED(rv
)) {
4447 NS_WARNING("EditorBase::CollapseSelectionTo() failed");
4450 return EditActionResult::HandledResult();
4453 nsresult
HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
4454 ComputeRangeToJoinBlockElementsInSameParent(
4455 const HTMLEditor
& aHTMLEditor
,
4456 nsIEditor::EDirection aDirectionAndAmount
, nsRange
& aRangeToDelete
,
4457 const Element
& aEditingHost
) const {
4458 MOZ_ASSERT(aHTMLEditor
.IsEditActionDataAvailable());
4459 MOZ_ASSERT(!aRangeToDelete
.Collapsed());
4460 MOZ_ASSERT(mMode
== Mode::JoinBlocksInSameParent
);
4461 MOZ_ASSERT(mLeftContent
);
4462 MOZ_ASSERT(mLeftContent
->IsElement());
4463 MOZ_ASSERT(aRangeToDelete
.GetStartContainer()->IsInclusiveDescendantOf(
4465 MOZ_ASSERT(mRightContent
);
4466 MOZ_ASSERT(mRightContent
->IsElement());
4468 aRangeToDelete
.GetEndContainer()->IsInclusiveDescendantOf(mRightContent
));
4469 MOZ_ASSERT(mLeftContent
->GetParentNode() == mRightContent
->GetParentNode());
4472 mDeleteRangesHandlerConst
.ComputeRangeToDeleteRangeWithTransaction(
4473 aHTMLEditor
, aDirectionAndAmount
, aRangeToDelete
, aEditingHost
);
4474 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
4475 "AutoDeleteRangesHandler::"
4476 "ComputeRangeToDeleteRangeWithTransaction() failed");
4480 Result
<EditActionResult
, nsresult
> HTMLEditor::AutoDeleteRangesHandler::
4481 AutoBlockElementsJoiner::JoinBlockElementsInSameParent(
4482 HTMLEditor
& aHTMLEditor
,
4483 const LimitersAndCaretData
& aLimitersAndCaretData
,
4484 nsIEditor::EDirection aDirectionAndAmount
,
4485 nsIEditor::EStripWrappers aStripWrappers
, nsRange
& aRangeToDelete
,
4486 SelectionWasCollapsed aSelectionWasCollapsed
,
4487 const Element
& aEditingHost
) {
4488 MOZ_ASSERT(aHTMLEditor
.IsEditActionDataAvailable());
4489 MOZ_ASSERT(!aRangeToDelete
.Collapsed());
4490 MOZ_ASSERT(mMode
== Mode::JoinBlocksInSameParent
);
4491 MOZ_ASSERT(mLeftContent
);
4492 MOZ_ASSERT(mLeftContent
->IsElement());
4493 MOZ_ASSERT(aRangeToDelete
.GetStartContainer()->IsInclusiveDescendantOf(
4495 MOZ_ASSERT(mRightContent
);
4496 MOZ_ASSERT(mRightContent
->IsElement());
4498 aRangeToDelete
.GetEndContainer()->IsInclusiveDescendantOf(mRightContent
));
4499 MOZ_ASSERT(mLeftContent
->GetParentNode() == mRightContent
->GetParentNode());
4501 const bool backspaceInRightBlock
=
4502 aSelectionWasCollapsed
== SelectionWasCollapsed::Yes
&&
4503 nsIEditor::DirectionIsBackspace(aDirectionAndAmount
);
4505 AutoClonedSelectionRangeArray
rangesToDelete(aRangeToDelete
,
4506 aLimitersAndCaretData
);
4507 Result
<CaretPoint
, nsresult
> caretPointOrError
=
4508 aHTMLEditor
.DeleteRangesWithTransaction(aDirectionAndAmount
,
4509 aStripWrappers
, rangesToDelete
);
4510 if (MOZ_UNLIKELY(caretPointOrError
.isErr())) {
4511 NS_WARNING("HTMLEditor::DeleteRangesWithTransaction() failed");
4512 return caretPointOrError
.propagateErr();
4515 nsresult rv
= caretPointOrError
.inspect().SuggestCaretPointTo(
4516 aHTMLEditor
, {SuggestCaret::OnlyIfHasSuggestion
,
4517 SuggestCaret::OnlyIfTransactionsAllowedToDoIt
,
4518 SuggestCaret::AndIgnoreTrivialError
});
4519 if (NS_FAILED(rv
)) {
4520 NS_WARNING("CaretPoint::SuggestCaretPointTo() failed");
4523 NS_WARNING_ASSERTION(rv
!= NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR
,
4524 "CaretPoint::SuggestCaretPointTo() failed, but ignored");
4526 if (NS_WARN_IF(!mLeftContent
->GetParentNode()) ||
4527 NS_WARN_IF(!mRightContent
->GetParentNode()) ||
4528 NS_WARN_IF(mLeftContent
->GetParentNode() !=
4529 mRightContent
->GetParentNode())) {
4530 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
);
4533 auto startOfRightContent
=
4534 HTMLEditUtils::GetDeepestEditableStartPointOf
<EditorDOMPoint
>(
4536 AutoTrackDOMPoint
trackStartOfRightContent(aHTMLEditor
.RangeUpdaterRef(),
4537 &startOfRightContent
);
4538 Result
<EditorDOMPoint
, nsresult
> atFirstChildOfTheLastRightNodeOrError
=
4539 JoinNodesDeepWithTransaction(aHTMLEditor
, MOZ_KnownLive(*mLeftContent
),
4540 MOZ_KnownLive(*mRightContent
));
4541 if (MOZ_UNLIKELY(atFirstChildOfTheLastRightNodeOrError
.isErr())) {
4542 NS_WARNING("HTMLEditor::JoinNodesDeepWithTransaction() failed");
4543 return atFirstChildOfTheLastRightNodeOrError
.propagateErr();
4545 MOZ_ASSERT(atFirstChildOfTheLastRightNodeOrError
.inspect().IsSet());
4546 trackStartOfRightContent
.FlushAndStopTracking();
4547 if (NS_WARN_IF(!startOfRightContent
.IsSet()) ||
4548 NS_WARN_IF(!startOfRightContent
.GetContainer()->IsInComposedDoc())) {
4549 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
);
4552 // If we're deleting selection (not replacing with new content) and the joined
4553 // point follows a text node, we should put caret to end of the preceding text
4554 // node because the other browsers insert following inputs into there.
4555 if (MayEditActionDeleteAroundCollapsedSelection(
4556 aHTMLEditor
.GetEditAction())) {
4557 WSRunScanner
scanner(&aEditingHost
, startOfRightContent
,
4558 BlockInlineCheck::UseComputedDisplayOutsideStyle
);
4559 const WSScanResult maybePreviousText
=
4560 scanner
.ScanPreviousVisibleNodeOrBlockBoundaryFrom(startOfRightContent
);
4561 if (maybePreviousText
.IsContentEditable() &&
4562 maybePreviousText
.InVisibleOrCollapsibleCharacters()) {
4563 nsresult rv
= aHTMLEditor
.CollapseSelectionTo(
4564 maybePreviousText
.PointAfterReachedContent
<EditorRawDOMPoint
>());
4565 if (NS_FAILED(rv
)) {
4566 NS_WARNING("EditorBase::CollapseSelectionTo() failed");
4569 // If we prefer to use style in the previous line, we should forget
4570 // previous styles since the caret position has all styles which we want
4571 // to use with new content.
4572 if (backspaceInRightBlock
) {
4573 aHTMLEditor
.TopLevelEditSubActionDataRef()
4574 .mCachedPendingStyles
->Clear();
4576 // And we don't want to keep extending a link at ex-end of the previous
4578 if (HTMLEditor::GetLinkElement(maybePreviousText
.TextPtr())) {
4579 aHTMLEditor
.mPendingStylesToApplyToNewContent
4580 ->ClearLinkAndItsSpecifiedStyle();
4582 return EditActionResult::HandledResult();
4586 // Otherwise, we should put caret at start of the right content.
4587 rv
= aHTMLEditor
.CollapseSelectionTo(
4588 atFirstChildOfTheLastRightNodeOrError
.inspect());
4589 if (NS_FAILED(rv
)) {
4590 NS_WARNING("EditorBase::CollapseSelectionTo() failed");
4593 return EditActionResult::HandledResult();
4596 Result
<bool, nsresult
>
4597 HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
4598 ComputeRangeToDeleteNodesEntirelyInRangeButKeepTableStructure(
4599 const HTMLEditor
& aHTMLEditor
, nsRange
& aRange
,
4600 AutoDeleteRangesHandler::SelectionWasCollapsed aSelectionWasCollapsed
)
4602 MOZ_ASSERT(aHTMLEditor
.IsEditActionDataAvailable());
4604 AutoTArray
<OwningNonNull
<nsIContent
>, 10> arrayOfTopChildren
;
4605 DOMSubtreeIterator iter
;
4606 nsresult rv
= iter
.Init(aRange
);
4607 if (NS_FAILED(rv
)) {
4608 NS_WARNING("DOMSubtreeIterator::Init() failed");
4611 iter
.AppendAllNodesToArray(arrayOfTopChildren
);
4612 return NeedsToJoinNodesAfterDeleteNodesEntirelyInRangeButKeepTableStructure(
4613 aHTMLEditor
, arrayOfTopChildren
, aSelectionWasCollapsed
);
4616 Result
<DeleteRangeResult
, nsresult
> HTMLEditor::AutoDeleteRangesHandler::
4617 AutoBlockElementsJoiner::DeleteNodesEntirelyInRangeButKeepTableStructure(
4618 HTMLEditor
& aHTMLEditor
,
4619 const nsTArray
<OwningNonNull
<nsIContent
>>& aArrayOfContent
,
4620 PutCaretTo aPutCaretTo
) {
4621 MOZ_ASSERT(aHTMLEditor
.IsEditActionDataAvailable());
4623 DeleteRangeResult deleteContentResult
= DeleteRangeResult::IgnoredResult();
4624 for (const auto& content
: aArrayOfContent
) {
4625 // XXX After here, the child contents in the array may have been moved
4626 // to somewhere or removed. We should handle it.
4628 // MOZ_KnownLive because 'aArrayOfContent' is guaranteed to
4630 AutoTrackDOMDeleteRangeResult
trackDeleteContentResult(
4631 aHTMLEditor
.RangeUpdaterRef(), &deleteContentResult
);
4632 Result
<DeleteRangeResult
, nsresult
> deleteResult
=
4633 DeleteContentButKeepTableStructure(aHTMLEditor
, MOZ_KnownLive(content
));
4634 if (MOZ_UNLIKELY(deleteResult
.isErr())) {
4635 if (NS_WARN_IF(deleteResult
.inspectErr() == NS_ERROR_EDITOR_DESTROYED
)) {
4636 return Err(NS_ERROR_EDITOR_DESTROYED
);
4639 "AutoBlockElementsJoiner::DeleteContentButKeepTableStructure() "
4640 "failed, but ignored");
4643 trackDeleteContentResult
.FlushAndStopTracking();
4644 deleteContentResult
|= deleteResult
.unwrap();
4646 if (deleteContentResult
.Handled()) {
4647 EditorDOMPoint pointToPutCaret
=
4648 aPutCaretTo
== PutCaretTo::StartOfRange
4649 ? deleteContentResult
.DeleteRangeRef().StartRef()
4650 : deleteContentResult
.DeleteRangeRef().EndRef();
4651 deleteContentResult
|= CaretPoint(std::move(pointToPutCaret
));
4653 return std::move(deleteContentResult
);
4656 bool HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
4657 NeedsToJoinNodesAfterDeleteNodesEntirelyInRangeButKeepTableStructure(
4658 const HTMLEditor
& aHTMLEditor
,
4659 const nsTArray
<OwningNonNull
<nsIContent
>>& aArrayOfContents
,
4660 AutoDeleteRangesHandler::SelectionWasCollapsed aSelectionWasCollapsed
)
4663 case Mode::DeletePrecedingLinesAndContentInRange
:
4664 case Mode::DeleteBRElement
:
4665 case Mode::DeletePrecedingBRElementOfBlock
:
4666 case Mode::DeletePrecedingPreformattedLineBreak
:
4672 // If original selection was collapsed, we need always to join the nodes.
4674 if (aSelectionWasCollapsed
==
4675 AutoDeleteRangesHandler::SelectionWasCollapsed::No
) {
4678 // If something visible is deleted, no need to join. Visible means
4679 // all nodes except non-visible textnodes and breaks.
4680 if (aArrayOfContents
.IsEmpty()) {
4683 for (const OwningNonNull
<nsIContent
>& content
: aArrayOfContents
) {
4684 if (content
->IsText()) {
4685 if (HTMLEditUtils::IsInVisibleTextFrames(aHTMLEditor
.GetPresContext(),
4686 *content
->AsText())) {
4691 // XXX If it's an element node, we should check whether it has visible
4693 if (!content
->IsElement() ||
4694 HTMLEditUtils::IsEmptyNode(
4695 *content
->AsElement(),
4696 {EmptyCheckOption::TreatSingleBRElementAsVisible
,
4697 EmptyCheckOption::TreatNonEditableContentAsInvisible
})) {
4700 if (!HTMLEditUtils::IsInvisibleBRElement(*content
)) {
4707 Result
<DeleteRangeResult
, nsresult
> HTMLEditor::AutoDeleteRangesHandler::
4708 AutoBlockElementsJoiner::DeleteTextAtStartAndEndOfRange(
4709 HTMLEditor
& aHTMLEditor
, nsRange
& aRange
, PutCaretTo aPutCaretTo
) {
4710 if (MOZ_UNLIKELY(aRange
.Collapsed())) {
4711 return DeleteRangeResult::IgnoredResult();
4714 const auto DeleteTextNode
=
4715 [&aHTMLEditor
](const OwningNonNull
<Text
>& aTextNode
)
4716 MOZ_NEVER_INLINE_DEBUG MOZ_CAN_RUN_SCRIPT
4717 -> Result
<DeleteRangeResult
, nsresult
> {
4718 const nsCOMPtr
<nsINode
> parentNode
= aTextNode
->GetParentNode();
4719 if (NS_WARN_IF(!parentNode
)) {
4720 return Err(NS_ERROR_FAILURE
);
4722 const nsCOMPtr
<nsIContent
> nextSibling
= aTextNode
->GetNextSibling();
4723 nsresult rv
= aHTMLEditor
.DeleteNodeWithTransaction(aTextNode
);
4724 if (NS_FAILED(rv
)) {
4725 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
4728 if (NS_WARN_IF(nextSibling
&& nextSibling
->GetParentNode() != parentNode
) ||
4729 NS_WARN_IF(!parentNode
->IsInComposedDoc())) {
4730 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
);
4732 const auto atRemovedTextNode
= nextSibling
4733 ? EditorDOMPoint(nextSibling
)
4734 : EditorDOMPoint::AtEndOf(*parentNode
);
4735 return DeleteRangeResult(EditorDOMRange(atRemovedTextNode
),
4739 EditorDOMRange
range(aRange
);
4740 // If the range is in a text node, delete middle of the text or the text node
4742 if (range
.StartRef().IsInTextNode() && range
.InSameContainer()) {
4743 const OwningNonNull
<Text
> textNode
= *range
.StartRef().ContainerAs
<Text
>();
4744 if (range
.StartRef().IsStartOfContainer() &&
4745 range
.EndRef().IsEndOfContainer()) {
4746 Result
<DeleteRangeResult
, nsresult
> deleteTextNodeResult
=
4747 DeleteTextNode(textNode
);
4748 NS_WARNING_ASSERTION(
4749 deleteTextNodeResult
.isOk(),
4750 "DeleteTextNode() failed to delete the selected Text node");
4751 return deleteTextNodeResult
;
4753 MOZ_ASSERT(range
.EndRef().Offset() - range
.StartRef().Offset() > 0);
4754 Result
<CaretPoint
, nsresult
> caretPointOrError
=
4755 aHTMLEditor
.DeleteTextWithTransaction(
4756 textNode
, range
.StartRef().Offset(),
4757 range
.EndRef().Offset() - range
.StartRef().Offset());
4758 if (MOZ_UNLIKELY(caretPointOrError
.isErr())) {
4759 NS_WARNING("HTMLEditor::DeleteTextWithTransaction() failed");
4760 return caretPointOrError
.propagateErr();
4762 const EditorDOMPoint atRemovedText
=
4763 caretPointOrError
.unwrap().UnwrapCaretPoint();
4764 if (NS_WARN_IF(!atRemovedText
.IsSetAndValidInComposedDoc())) {
4765 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
);
4767 return DeleteRangeResult(EditorDOMRange(atRemovedText
), atRemovedText
);
4770 // If the range starts in a text node and ends in a different node, delete
4771 // the text after the start boundary.
4772 auto deleteStartTextResultOrError
=
4773 [&]() MOZ_NEVER_INLINE_DEBUG MOZ_CAN_RUN_SCRIPT
4774 -> Result
<DeleteRangeResult
, nsresult
> {
4775 if (!range
.StartRef().IsInTextNode() ||
4776 range
.StartRef().IsEndOfContainer()) {
4777 return DeleteRangeResult::IgnoredResult();
4779 AutoTrackDOMRange
trackRange(aHTMLEditor
.RangeUpdaterRef(), &range
);
4780 const OwningNonNull
<Text
> textNode
= *range
.StartRef().ContainerAs
<Text
>();
4781 if (range
.StartRef().IsStartOfContainer()) {
4782 Result
<DeleteRangeResult
, nsresult
> deleteTextNodeResult
=
4783 DeleteTextNode(textNode
);
4784 NS_WARNING_ASSERTION(
4785 deleteTextNodeResult
.isOk(),
4786 "DeleteTextNode() failed to delete the start Text node");
4787 return deleteTextNodeResult
;
4789 Result
<CaretPoint
, nsresult
> caretPointOrError
=
4790 aHTMLEditor
.DeleteTextWithTransaction(
4791 textNode
, range
.StartRef().Offset(),
4792 textNode
->TextDataLength() - range
.StartRef().Offset());
4793 if (MOZ_UNLIKELY(caretPointOrError
.isErr())) {
4794 NS_WARNING("HTMLEditor::DeleteTextWithTransaction() failed");
4795 return caretPointOrError
.propagateErr();
4797 const EditorDOMPoint atRemovedText
=
4798 caretPointOrError
.unwrap().UnwrapCaretPoint();
4799 if (NS_WARN_IF(!atRemovedText
.IsSetAndValidInComposedDoc())) {
4800 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
);
4802 return DeleteRangeResult(EditorDOMRange(atRemovedText
), atRemovedText
);
4804 if (MOZ_UNLIKELY(deleteStartTextResultOrError
.isErr())) {
4805 return deleteStartTextResultOrError
.propagateErr();
4807 DeleteRangeResult deleteStartTextResult
=
4808 deleteStartTextResultOrError
.unwrap();
4810 // If the range ends in a text node and starts from a different node, delete
4811 // the text before the end boundary.
4812 auto deleteEndTextResultOrError
=
4813 [&]() MOZ_NEVER_INLINE_DEBUG MOZ_CAN_RUN_SCRIPT
4814 -> Result
<DeleteRangeResult
, nsresult
> {
4815 if (!range
.EndRef().IsInTextNode() || range
.EndRef().IsStartOfContainer()) {
4816 return DeleteRangeResult::IgnoredResult();
4818 AutoTrackDOMRange
trackRange(aHTMLEditor
.RangeUpdaterRef(), &range
);
4819 AutoTrackDOMDeleteRangeResult
trackDeleteStartTextResult(
4820 aHTMLEditor
.RangeUpdaterRef(), &deleteStartTextResult
);
4821 const OwningNonNull
<Text
> textNode
= *range
.EndRef().ContainerAs
<Text
>();
4822 if (range
.EndRef().IsEndOfContainer()) {
4823 Result
<DeleteRangeResult
, nsresult
> deleteTextNodeResult
=
4824 DeleteTextNode(textNode
);
4825 NS_WARNING_ASSERTION(
4826 deleteTextNodeResult
.isOk(),
4827 "DeleteTextNode() failed to delete the end Text node");
4828 return deleteTextNodeResult
;
4830 Result
<CaretPoint
, nsresult
> caretPointOrError
=
4831 aHTMLEditor
.DeleteTextWithTransaction(textNode
, 0,
4832 range
.EndRef().Offset());
4833 if (MOZ_UNLIKELY(caretPointOrError
.isErr())) {
4834 NS_WARNING("HTMLEditor::DeleteTextWithTransaction() failed");
4835 return caretPointOrError
.propagateErr();
4837 const EditorDOMPoint atRemovedText
=
4838 caretPointOrError
.unwrap().UnwrapCaretPoint();
4839 if (NS_WARN_IF(!atRemovedText
.IsSetAndValidInComposedDoc())) {
4840 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
);
4842 return DeleteRangeResult(EditorDOMRange(atRemovedText
), atRemovedText
);
4844 if (MOZ_UNLIKELY(deleteEndTextResultOrError
.isErr())) {
4845 return deleteEndTextResultOrError
.propagateErr();
4847 DeleteRangeResult deleteEndTextResult
= deleteEndTextResultOrError
.unwrap();
4849 if (!deleteStartTextResult
.Handled() && !deleteEndTextResult
.Handled()) {
4850 deleteStartTextResult
.IgnoreCaretPointSuggestion();
4851 deleteEndTextResult
.IgnoreCaretPointSuggestion();
4852 return DeleteRangeResult::IgnoredResult();
4855 EditorDOMPoint pointToPutCaret
=
4856 aPutCaretTo
== PutCaretTo::EndOfRange
4857 ? (deleteEndTextResult
.Handled()
4858 ? deleteEndTextResult
.UnwrapCaretPoint()
4860 : (deleteStartTextResult
.Handled()
4861 ? deleteStartTextResult
.UnwrapCaretPoint()
4862 : EditorDOMPoint());
4863 deleteStartTextResult
|= deleteEndTextResult
;
4864 deleteStartTextResult
.ForgetCaretPointSuggestion();
4865 if (pointToPutCaret
.IsSet()) {
4866 deleteStartTextResult
|= CaretPoint(std::move(pointToPutCaret
));
4868 return std::move(deleteStartTextResult
);
4872 template <typename EditorDOMPointType
>
4873 Result
<Element
*, nsresult
> HTMLEditor::AutoDeleteRangesHandler::
4874 AutoBlockElementsJoiner::GetMostDistantBlockAncestorIfPointIsStartAtBlock(
4875 const EditorDOMPointType
& aPoint
, const Element
& aEditingHost
,
4876 const Element
* aAncestorLimiter
/* = nullptr */) {
4877 MOZ_ASSERT(aPoint
.IsSetAndValid());
4878 MOZ_ASSERT(aPoint
.IsInComposedDoc());
4880 if (!aAncestorLimiter
) {
4881 aAncestorLimiter
= &aEditingHost
;
4884 const auto ReachedCurrentBlockBoundaryWhichWeCanCross
=
4885 [&aEditingHost
, aAncestorLimiter
](const WSScanResult
& aScanResult
) {
4886 // When the scan result is "reached current block boundary", it may not
4888 return aScanResult
.ReachedCurrentBlockBoundary() &&
4889 HTMLEditUtils::IsRemovableFromParentNode(
4890 *aScanResult
.ElementPtr()) &&
4891 aScanResult
.ElementPtr() != &aEditingHost
&&
4892 aScanResult
.ElementPtr() != aAncestorLimiter
&&
4893 // Don't cross <body>, <head> and <html>
4894 !aScanResult
.ElementPtr()->IsAnyOfHTMLElements(
4895 nsGkAtoms::body
, nsGkAtoms::head
, nsGkAtoms::html
) &&
4896 // Don't cross table elements
4897 !HTMLEditUtils::IsAnyTableElement(aScanResult
.ElementPtr());
4900 const WSScanResult prevVisibleThing
=
4901 WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary(
4902 aAncestorLimiter
, aPoint
,
4903 BlockInlineCheck::UseComputedDisplayOutsideStyle
);
4904 if (!ReachedCurrentBlockBoundaryWhichWeCanCross(prevVisibleThing
)) {
4907 MOZ_ASSERT(HTMLEditUtils::IsBlockElement(
4908 *prevVisibleThing
.ElementPtr(),
4909 BlockInlineCheck::UseComputedDisplayOutsideStyle
));
4910 for (Element
* ancestorBlock
= prevVisibleThing
.ElementPtr(); ancestorBlock
;) {
4911 const WSScanResult prevVisibleThing
=
4912 WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary(
4913 aAncestorLimiter
, EditorRawDOMPoint(ancestorBlock
),
4914 BlockInlineCheck::UseComputedDisplayOutsideStyle
);
4915 if (!ReachedCurrentBlockBoundaryWhichWeCanCross(prevVisibleThing
)) {
4916 return ancestorBlock
;
4918 MOZ_ASSERT(HTMLEditUtils::IsBlockElement(
4919 *prevVisibleThing
.ElementPtr(),
4920 BlockInlineCheck::UseComputedDisplayOutsideStyle
));
4921 ancestorBlock
= prevVisibleThing
.ElementPtr();
4923 return Err(NS_ERROR_FAILURE
);
4926 void HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
4927 ExtendRangeToDeleteNonCollapsedRange(
4928 const HTMLEditor
& aHTMLEditor
, nsRange
& aRangeToDelete
,
4929 const Element
& aEditingHost
, ComputeRangeFor aComputeRangeFor
) const {
4930 MOZ_ASSERT_IF(aComputeRangeFor
== ComputeRangeFor::GetTargetRanges
,
4931 aRangeToDelete
.IsPositioned());
4932 MOZ_ASSERT(!aRangeToDelete
.Collapsed());
4933 MOZ_ASSERT(mLeftContent
);
4934 MOZ_ASSERT(mLeftContent
->IsElement());
4935 MOZ_ASSERT(aRangeToDelete
.GetStartContainer()->IsInclusiveDescendantOf(
4937 MOZ_ASSERT(mRightContent
);
4938 MOZ_ASSERT(mRightContent
->IsElement());
4940 aRangeToDelete
.GetEndContainer()->IsInclusiveDescendantOf(mRightContent
));
4942 const DebugOnly
<Result
<bool, nsresult
>> extendRangeResult
=
4943 AutoDeleteRangesHandler::
4944 ExtendRangeToContainAncestorInlineElementsAtStart(aRangeToDelete
,
4946 NS_WARNING_ASSERTION(extendRangeResult
.value
.isOk(),
4947 "AutoDeleteRangesHandler::"
4948 "ExtendRangeToContainAncestorInlineElementsAtStart() "
4949 "failed, but ignored");
4950 if (mMode
!= Mode::DeletePrecedingLinesAndContentInRange
) {
4954 // If we're computing for beforeinput.getTargetRanges() and the inputType
4955 // is not a simple deletion like replacing selected content with new
4956 // content, the range should end at the original end boundary of the given
4957 // range even if we're deleting only preceding lines of the right child
4959 const bool preserveEndBoundary
=
4960 aComputeRangeFor
== ComputeRangeFor::GetTargetRanges
&&
4961 !MayEditActionDeleteAroundCollapsedSelection(aHTMLEditor
.GetEditAction());
4962 // We need to delete only the preceding lines of the right block. Therefore,
4963 // we need to shrink the range to ends before the right block if the range
4964 // does not contain any meaningful content in the right block.
4965 const Result
<Element
*, nsresult
> inclusiveAncestorCurrentBlockOrError
=
4966 AutoBlockElementsJoiner::GetMostDistantBlockAncestorIfPointIsStartAtBlock(
4967 EditorRawDOMPoint(aRangeToDelete
.EndRef()), aEditingHost
,
4968 mLeftContent
->AsElement());
4969 MOZ_ASSERT(inclusiveAncestorCurrentBlockOrError
.isOk());
4970 MOZ_ASSERT_IF(inclusiveAncestorCurrentBlockOrError
.inspect(),
4971 mRightContent
->IsInclusiveDescendantOf(
4972 inclusiveAncestorCurrentBlockOrError
.inspect()));
4973 if (MOZ_UNLIKELY(!inclusiveAncestorCurrentBlockOrError
.isOk() ||
4974 !inclusiveAncestorCurrentBlockOrError
.inspect())) {
4978 const WSScanResult prevVisibleThingOfStartBoundary
=
4979 WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary(
4980 &aEditingHost
, EditorRawDOMPoint(aRangeToDelete
.StartRef()),
4981 BlockInlineCheck::UseComputedDisplayOutsideStyle
);
4982 // If the range starts after an invisible <br> of empty line immediately
4983 // before the most distant inclusive ancestor of the right block like
4984 // `<br><br>{<div>]abc`, we should delete the last empty line because
4985 // users won't see any reaction of the builtin editor in this case.
4986 if (prevVisibleThingOfStartBoundary
.ReachedBRElement() ||
4987 prevVisibleThingOfStartBoundary
.ReachedPreformattedLineBreak()) {
4988 const WSScanResult prevVisibleThingOfPreviousLineBreak
=
4989 WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary(
4991 prevVisibleThingOfStartBoundary
4992 .PointAtReachedContent
<EditorRawDOMPoint
>(),
4993 BlockInlineCheck::UseComputedDisplayOutsideStyle
);
4994 const WSScanResult nextVisibleThingOfPreviousBR
=
4995 WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary(
4997 prevVisibleThingOfStartBoundary
4998 .PointAfterReachedContent
<EditorRawDOMPoint
>(),
4999 BlockInlineCheck::UseComputedDisplayOutsideStyle
);
5000 if ((prevVisibleThingOfPreviousLineBreak
.ReachedBRElement() ||
5001 prevVisibleThingOfPreviousLineBreak
.ReachedPreformattedLineBreak()) &&
5002 nextVisibleThingOfPreviousBR
.ReachedOtherBlockElement() &&
5003 nextVisibleThingOfPreviousBR
.ElementPtr() ==
5004 inclusiveAncestorCurrentBlockOrError
.inspect()) {
5005 aRangeToDelete
.SetStart(prevVisibleThingOfStartBoundary
5006 .PointAtReachedContent
<EditorRawDOMPoint
>()
5007 .ToRawRangeBoundary(),
5012 if (preserveEndBoundary
) {
5016 if (aComputeRangeFor
== ComputeRangeFor::GetTargetRanges
) {
5017 // When we set the end boundary to around the right block, the new end
5018 // boundary should not after inline ancestors of the line break which won't
5020 const WSScanResult lastVisibleThingBeforeRightChildBlock
=
5021 [&]() -> WSScanResult
{
5022 EditorRawDOMPoint
scanStartPoint(aRangeToDelete
.StartRef());
5023 WSScanResult lastScanResult
= WSScanResult::Error();
5025 WSScanResult scanResult
=
5026 WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary(
5027 mLeftContent
->AsElement(), scanStartPoint
,
5028 BlockInlineCheck::UseComputedDisplayOutsideStyle
);
5029 if (scanResult
.ReachedBlockBoundary() ||
5030 scanResult
.ReachedInlineEditingHostBoundary()) {
5031 return lastScanResult
;
5034 scanResult
.PointAfterReachedContent
<EditorRawDOMPoint
>();
5035 lastScanResult
= scanResult
;
5038 if (lastVisibleThingBeforeRightChildBlock
.GetContent()) {
5039 const nsIContent
* commonAncestor
= nsIContent::FromNode(
5040 nsContentUtils::GetClosestCommonInclusiveAncestor(
5041 aRangeToDelete
.StartRef().Container(),
5042 lastVisibleThingBeforeRightChildBlock
.GetContent()));
5043 MOZ_ASSERT(commonAncestor
);
5044 if (commonAncestor
&&
5045 !mRightContent
->IsInclusiveDescendantOf(commonAncestor
)) {
5046 IgnoredErrorResult error
;
5047 aRangeToDelete
.SetEnd(
5048 EditorRawDOMPoint::AtEndOf(*commonAncestor
).ToRawRangeBoundary(),
5050 NS_WARNING_ASSERTION(!error
.Failed(),
5051 "nsRange::SetEnd() failed, but ignored");
5057 IgnoredErrorResult error
;
5058 aRangeToDelete
.SetEnd(
5059 EditorRawDOMPoint(inclusiveAncestorCurrentBlockOrError
.inspect())
5060 .ToRawRangeBoundary(),
5062 NS_WARNING_ASSERTION(!error
.Failed(),
5063 "nsRange::SetEnd() failed, but ignored");
5066 nsresult
HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
5067 ComputeRangeToDeleteNonCollapsedRange(
5068 const HTMLEditor
& aHTMLEditor
,
5069 nsIEditor::EDirection aDirectionAndAmount
, nsRange
& aRangeToDelete
,
5070 AutoDeleteRangesHandler::SelectionWasCollapsed aSelectionWasCollapsed
,
5071 const Element
& aEditingHost
) const {
5072 MOZ_ASSERT(aHTMLEditor
.IsEditActionDataAvailable());
5073 MOZ_ASSERT(!aRangeToDelete
.Collapsed());
5074 MOZ_ASSERT(mLeftContent
);
5075 MOZ_ASSERT(mLeftContent
->IsElement());
5076 MOZ_ASSERT(aRangeToDelete
.GetStartContainer()->IsInclusiveDescendantOf(
5078 MOZ_ASSERT(mRightContent
);
5079 MOZ_ASSERT(mRightContent
->IsElement());
5081 aRangeToDelete
.GetEndContainer()->IsInclusiveDescendantOf(mRightContent
));
5083 ExtendRangeToDeleteNonCollapsedRange(aHTMLEditor
, aRangeToDelete
,
5085 ComputeRangeFor::GetTargetRanges
);
5087 Result
<bool, nsresult
> result
=
5088 ComputeRangeToDeleteNodesEntirelyInRangeButKeepTableStructure(
5089 aHTMLEditor
, aRangeToDelete
, aSelectionWasCollapsed
);
5090 if (result
.isErr()) {
5092 "AutoBlockElementsJoiner::"
5093 "ComputeRangeToDeleteNodesEntirelyInRangeButKeepTableStructure() "
5095 return result
.unwrapErr();
5097 if (!result
.unwrap()) {
5101 AutoInclusiveAncestorBlockElementsJoiner
joiner(*mLeftContent
,
5103 Result
<bool, nsresult
> canJoinThem
=
5104 joiner
.Prepare(aHTMLEditor
, aEditingHost
);
5105 if (canJoinThem
.isErr()) {
5106 NS_WARNING("AutoInclusiveAncestorBlockElementsJoiner::Prepare() failed");
5107 return canJoinThem
.unwrapErr();
5110 if (!canJoinThem
.unwrap()) {
5111 return NS_SUCCESS_DOM_NO_OPERATION
;
5114 if (!joiner
.CanJoinBlocks()) {
5118 nsresult rv
= joiner
.ComputeRangeToDelete(aHTMLEditor
, EditorDOMPoint(),
5120 NS_WARNING_ASSERTION(
5122 "AutoInclusiveAncestorBlockElementsJoiner::ComputeRangeToDelete() "
5125 // FIXME: If we'll delete unnecessary following <br>, we need to include it
5126 // into aRangesToDelete.
5131 Result
<EditActionResult
, nsresult
> HTMLEditor::AutoDeleteRangesHandler::
5132 AutoBlockElementsJoiner::HandleDeleteNonCollapsedRange(
5133 HTMLEditor
& aHTMLEditor
, nsIEditor::EDirection aDirectionAndAmount
,
5134 nsIEditor::EStripWrappers aStripWrappers
, nsRange
& aRangeToDelete
,
5135 AutoDeleteRangesHandler::SelectionWasCollapsed aSelectionWasCollapsed
,
5136 const Element
& aEditingHost
) {
5137 MOZ_ASSERT(aHTMLEditor
.IsEditActionDataAvailable());
5138 MOZ_ASSERT(!aRangeToDelete
.Collapsed());
5139 MOZ_ASSERT(mDeleteRangesHandler
);
5141 const bool isDeletingLineBreak
=
5142 mMode
== Mode::DeleteBRElement
||
5143 mMode
== Mode::DeletePrecedingBRElementOfBlock
||
5144 mMode
== Mode::DeletePrecedingPreformattedLineBreak
;
5145 if (!isDeletingLineBreak
) {
5146 MOZ_ASSERT(aRangeToDelete
.GetStartContainer()->IsInclusiveDescendantOf(
5148 MOZ_ASSERT(aRangeToDelete
.GetEndContainer()->IsInclusiveDescendantOf(
5150 ExtendRangeToDeleteNonCollapsedRange(aHTMLEditor
, aRangeToDelete
,
5152 ComputeRangeFor::ToDeleteTheRange
);
5155 const bool backspaceInRightBlock
=
5156 aSelectionWasCollapsed
== SelectionWasCollapsed::Yes
&&
5157 nsIEditor::DirectionIsBackspace(aDirectionAndAmount
);
5159 AutoTArray
<OwningNonNull
<nsIContent
>, 10> arrayOfTopChildren
;
5161 DOMSubtreeIterator iter
;
5162 nsresult rv
= iter
.Init(aRangeToDelete
);
5163 if (NS_FAILED(rv
)) {
5164 NS_WARNING("DOMSubtreeIterator::Init() failed");
5167 iter
.AppendAllNodesToArray(arrayOfTopChildren
);
5170 const bool needsToJoinLater
=
5171 NeedsToJoinNodesAfterDeleteNodesEntirelyInRangeButKeepTableStructure(
5172 aHTMLEditor
, arrayOfTopChildren
, aSelectionWasCollapsed
);
5173 const bool joinInclusiveAncestorBlockElements
=
5174 !isDeletingLineBreak
&& needsToJoinLater
;
5175 const bool maybeDeleteOnlyFollowingContentOfFollowingBlockBoundary
=
5176 !isDeletingLineBreak
&&
5177 mMode
!= Mode::DeletePrecedingLinesAndContentInRange
&&
5178 HTMLEditUtils::PointIsImmediatelyBeforeCurrentBlockBoundary(
5179 EditorRawDOMPoint(aRangeToDelete
.StartRef()),
5180 HTMLEditUtils::IgnoreInvisibleLineBreak::Yes
, aEditingHost
);
5181 const PutCaretTo putCaretTo
= [&]() {
5182 // When we delete only preceding lines of the right child block, we should
5183 // put caret into start of the right block.
5184 if (mMode
== Mode::DeletePrecedingLinesAndContentInRange
) {
5185 return PutCaretTo::EndOfRange
;
5187 // If we're joining blocks: if deleting forward the selection should be
5188 // collapsed to the end of the selection, if deleting backward the selection
5189 // should be collapsed to the beginning of the selection.
5190 if (joinInclusiveAncestorBlockElements
) {
5191 return nsIEditor::DirectionIsDelete(aDirectionAndAmount
)
5192 ? PutCaretTo::EndOfRange
5193 : PutCaretTo::StartOfRange
;
5195 // But if we're not joining then the selection should collapse to the
5196 // beginning of the selection if we're deleting forward, because the end of
5197 // the selection will still be in the next block. And same thing for
5198 // deleting backwards (selection should collapse to the end, because the
5199 // beginning will still be in the first block). See Bug 507936.
5200 return nsIEditor::DirectionIsDelete(aDirectionAndAmount
)
5201 ? PutCaretTo::StartOfRange
5202 : PutCaretTo::EndOfRange
;
5205 auto deleteContentResultOrError
=
5206 [&]() MOZ_NEVER_INLINE_DEBUG MOZ_CAN_RUN_SCRIPT
5207 -> Result
<DeleteRangeResult
, nsresult
> {
5208 OwningNonNull
<nsRange
> rangeToDelete(aRangeToDelete
);
5209 AutoTrackDOMRange
trackRangeToDelete(aHTMLEditor
.RangeUpdaterRef(),
5212 // First, delete nodes which are entirely selected except table structure
5213 // elements like <td>, <th>, <caption>.
5214 Result
<DeleteRangeResult
, nsresult
> deleteResultOrError
=
5215 DeleteNodesEntirelyInRangeButKeepTableStructure(
5216 aHTMLEditor
, arrayOfTopChildren
, putCaretTo
);
5217 if (MOZ_UNLIKELY(deleteResultOrError
.isErr())) {
5219 "AutoBlockElementsJoiner::"
5220 "DeleteNodesEntirelyInRangeButKeepTableStructure() failed");
5221 return deleteResultOrError
.propagateErr();
5223 DeleteRangeResult deleteResult
= deleteResultOrError
.unwrap();
5224 // We'll compute caret position below, so, we don't need the caret point
5225 // suggestion of DeleteNodesEntirelyInRangeButKeepTableStructure().
5226 deleteResult
.ForgetCaretPointSuggestion();
5228 // Check endpoints for possible text deletion. We can assume that if
5229 // text node is found, we can delete to end or to beginning as
5230 // appropriate, since the case where both sel endpoints in same text
5231 // node was already handled (we wouldn't be here)
5232 AutoTrackDOMDeleteRangeResult
trackDeleteResult(
5233 aHTMLEditor
.RangeUpdaterRef(), &deleteResult
);
5234 Result
<DeleteRangeResult
, nsresult
> deleteSurroundingTextResultOrError
=
5235 DeleteTextAtStartAndEndOfRange(aHTMLEditor
, rangeToDelete
, putCaretTo
);
5236 if (MOZ_UNLIKELY(deleteSurroundingTextResultOrError
.isErr())) {
5238 "AutoBlockElementsJoiner::DeleteTextAtStartAndEndOfRange() failed");
5239 return deleteSurroundingTextResultOrError
.propagateErr();
5241 trackDeleteResult
.FlushAndStopTracking();
5243 DeleteRangeResult deleteSurroundingTextResult
=
5244 deleteSurroundingTextResultOrError
.unwrap();
5245 // We'll compute caret position below, so, we don't need the caret point
5246 // suggestion of DeleteTextAtStartAndEndOfRange().
5247 deleteSurroundingTextResult
.ForgetCaretPointSuggestion();
5249 // Merge the deleted range.
5250 deleteResult
|= deleteSurroundingTextResult
;
5252 if (mRightContent
&& mMode
== Mode::DeletePrecedingLinesAndContentInRange
) {
5253 if (NS_WARN_IF(!mRightContent
->IsInComposedDoc())) {
5254 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
);
5256 auto pointToPutCaret
=
5257 HTMLEditUtils::GetDeepestEditableStartPointOf
<EditorDOMPoint
>(
5259 MOZ_ASSERT(pointToPutCaret
.IsSet());
5260 deleteResult
|= CaretPoint(std::move(pointToPutCaret
));
5262 return std::move(deleteResult
);
5264 if (MOZ_UNLIKELY(deleteContentResultOrError
.isErr())) {
5265 return deleteContentResultOrError
.propagateErr();
5267 DeleteRangeResult deleteContentResult
= deleteContentResultOrError
.unwrap();
5268 // HandleDeleteLineBreak() should handle the new caret position by itself.
5269 if (isDeletingLineBreak
) {
5270 MOZ_ASSERT(!joinInclusiveAncestorBlockElements
);
5271 deleteContentResult
.IgnoreCaretPointSuggestion();
5272 return EditActionResult::HandledResult();
5275 auto moveFirstLineResultOrError
=
5276 [&]() MOZ_NEVER_INLINE_DEBUG MOZ_CAN_RUN_SCRIPT
5277 -> Result
<DeleteRangeResult
, nsresult
> {
5278 if (!joinInclusiveAncestorBlockElements
) {
5279 return DeleteRangeResult::IgnoredResult();
5282 MOZ_ASSERT(mLeftContent
);
5283 MOZ_ASSERT(mLeftContent
->IsElement());
5284 MOZ_ASSERT(mRightContent
);
5285 MOZ_ASSERT(mRightContent
->IsElement());
5287 if (!joinInclusiveAncestorBlockElements
) {
5288 return DeleteRangeResult::IgnoredResult();
5291 // Finally, join elements containing either mLeftContent or mRightContent.
5292 // XXX This may join only inline elements despite its name.
5293 AutoInclusiveAncestorBlockElementsJoiner
joiner(*mLeftContent
,
5295 Result
<bool, nsresult
> canJoinThem
=
5296 joiner
.Prepare(aHTMLEditor
, aEditingHost
);
5297 if (canJoinThem
.isErr()) {
5298 NS_WARNING("AutoInclusiveAncestorBlockElementsJoiner::Prepare() failed");
5299 return canJoinThem
.propagateErr();
5302 if (!canJoinThem
.inspect() || !joiner
.CanJoinBlocks()) {
5303 return DeleteRangeResult::IgnoredResult();
5306 OwningNonNull
<nsRange
> rangeToDelete(aRangeToDelete
);
5307 AutoTrackDOMRange
trackRangeToDelete(aHTMLEditor
.RangeUpdaterRef(),
5309 AutoTrackDOMDeleteRangeResult
trackDeleteContentResult(
5310 aHTMLEditor
.RangeUpdaterRef(), &deleteContentResult
);
5311 Result
<DeleteRangeResult
, nsresult
> moveFirstLineResultOrError
=
5312 joiner
.Run(aHTMLEditor
, aEditingHost
);
5313 if (MOZ_UNLIKELY(moveFirstLineResultOrError
.isErr())) {
5314 NS_WARNING("AutoInclusiveAncestorBlockElementsJoiner::Run() failed");
5315 return moveFirstLineResultOrError
.propagateErr();
5317 trackDeleteContentResult
.FlushAndStopTracking();
5318 DeleteRangeResult moveFirstLineResult
= moveFirstLineResultOrError
.unwrap();
5320 if (joiner
.ShouldDeleteLeafContentInstead()) {
5321 NS_ASSERTION(moveFirstLineResult
.Ignored(),
5322 "Assumed `AutoInclusiveAncestorBlockElementsJoiner::Run()` "
5323 "returning ignored, but returned not ignored");
5325 NS_ASSERTION(!moveFirstLineResult
.Ignored(),
5326 "Assumed `AutoInclusiveAncestorBlockElementsJoiner::Run()` "
5327 "returning handled, but returned ignored");
5329 #endif // #ifdef DEBUG
5330 return std::move(moveFirstLineResult
);
5332 if (MOZ_UNLIKELY(moveFirstLineResultOrError
.isErr())) {
5333 deleteContentResult
.IgnoreCaretPointSuggestion();
5334 return moveFirstLineResultOrError
.propagateErr();
5336 DeleteRangeResult moveFirstLineResult
= moveFirstLineResultOrError
.unwrap();
5338 auto pointToPutCaret
= [&]() MOZ_NEVER_INLINE_DEBUG
-> EditorDOMPoint
{
5339 if (moveFirstLineResult
.HasCaretPointSuggestion()) {
5340 MOZ_ASSERT(moveFirstLineResult
.Handled());
5341 if (MayEditActionDeleteAroundCollapsedSelection(
5342 aHTMLEditor
.GetEditAction())) {
5343 deleteContentResult
.IgnoreCaretPointSuggestion();
5344 // If we're deleting selection (not replacing with new content) and
5345 // AutoInclusiveAncestorBlockElementsJoiner computed new caret position,
5346 // we should use it.
5347 return moveFirstLineResult
.UnwrapCaretPoint();
5349 moveFirstLineResult
.IgnoreCaretPointSuggestion();
5351 if (deleteContentResult
.HasCaretPointSuggestion()) {
5352 return deleteContentResult
.UnwrapCaretPoint();
5354 return EditorDOMPoint(putCaretTo
== PutCaretTo::StartOfRange
5355 ? aRangeToDelete
.StartRef()
5356 : aRangeToDelete
.EndRef());
5358 MOZ_ASSERT(pointToPutCaret
.IsSetAndValidInComposedDoc());
5361 AutoTrackDOMPoint
trackPointToPutCaret(aHTMLEditor
.RangeUpdaterRef(),
5363 nsresult rv
= mDeleteRangesHandler
->DeleteUnnecessaryNodes(
5364 aHTMLEditor
, EditorDOMRange(aRangeToDelete
), aEditingHost
);
5365 if (NS_FAILED(rv
)) {
5366 NS_WARNING("AutoDeleteRangesHandler::DeleteUnnecessaryNodes() failed");
5369 trackPointToPutCaret
.FlushAndStopTracking();
5370 if (NS_WARN_IF(!pointToPutCaret
.IsSetAndValidInComposedDoc())) {
5371 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
);
5375 if (aHTMLEditor
.IsMailEditor() &&
5376 MOZ_LIKELY(pointToPutCaret
.IsInContentNode())) {
5377 AutoTrackDOMPoint
trackPointToPutCaret(aHTMLEditor
.RangeUpdaterRef(),
5379 nsresult rv
= aHTMLEditor
.DeleteMostAncestorMailCiteElementIfEmpty(
5380 MOZ_KnownLive(*pointToPutCaret
.ContainerAs
<nsIContent
>()));
5381 if (NS_FAILED(rv
)) {
5383 "HTMLEditor::DeleteMostAncestorMailCiteElementIfEmpty() failed");
5386 trackPointToPutCaret
.FlushAndStopTracking();
5387 if (NS_WARN_IF(!pointToPutCaret
.IsSetAndValidInComposedDoc())) {
5388 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
);
5392 const auto EnsureNoFollowingUnnecessaryLineBreak
=
5393 [&](const EditorDOMPoint
& aPoint
)
5394 MOZ_NEVER_INLINE_DEBUG MOZ_CAN_RUN_SCRIPT
{
5395 if (!aPoint
.IsInContentNode()) {
5398 AutoTrackDOMDeleteRangeResult
trackDeleteContentResult(
5399 aHTMLEditor
.RangeUpdaterRef(), &deleteContentResult
);
5400 AutoTrackDOMDeleteRangeResult
trackMoveFirstLineResult(
5401 aHTMLEditor
.RangeUpdaterRef(), &moveFirstLineResult
);
5402 AutoTrackDOMPoint
trackPointToPutCaret(
5403 aHTMLEditor
.RangeUpdaterRef(), &pointToPutCaret
);
5404 nsresult rv
= aHTMLEditor
.EnsureNoFollowingUnnecessaryLineBreak(
5405 aPoint
, aEditingHost
);
5406 NS_WARNING_ASSERTION(
5408 "HTMLEditor::EnsureNoFollowingUnnecessaryLineBreak() failed");
5412 const auto InsertPaddingBRElementIfNeeded
=
5413 [&](const EditorDOMPoint
& aPoint
)
5414 MOZ_NEVER_INLINE_DEBUG MOZ_CAN_RUN_SCRIPT
5415 -> Result
<CaretPoint
, nsresult
> {
5416 if (!aPoint
.IsInContentNode()) {
5417 return CaretPoint(EditorDOMPoint());
5419 const bool insertingAtCaretPoint
= aPoint
== pointToPutCaret
;
5420 if (insertingAtCaretPoint
&& aHTMLEditor
.GetTopLevelEditSubAction() !=
5421 EditSubAction::eDeleteSelectedContent
) {
5422 return CaretPoint(EditorDOMPoint());
5424 if (!insertingAtCaretPoint
&&
5425 mMode
== Mode::DeletePrecedingLinesAndContentInRange
) {
5426 return CaretPoint(EditorDOMPoint());
5428 AutoTrackDOMDeleteRangeResult
trackDeleteContentResult(
5429 aHTMLEditor
.RangeUpdaterRef(), &deleteContentResult
);
5430 AutoTrackDOMDeleteRangeResult
trackMoveFirstLineResult(
5431 aHTMLEditor
.RangeUpdaterRef(), &moveFirstLineResult
);
5432 AutoTrackDOMPoint
trackPointToPutCaret(aHTMLEditor
.RangeUpdaterRef(),
5434 Result
<CreateLineBreakResult
, nsresult
> insertPaddingBRElementOrError
=
5435 aHTMLEditor
.InsertPaddingBRElementIfNeeded(
5437 aEditingHost
.IsContentEditablePlainTextOnly() ? nsIEditor::eNoStrip
5438 : nsIEditor::eStrip
,
5440 if (MOZ_UNLIKELY(insertPaddingBRElementOrError
.isErr())) {
5441 NS_WARNING("HTMLEditor::InsertPaddingBRElementIfNeeded() failed");
5442 return insertPaddingBRElementOrError
.propagateErr();
5444 CreateLineBreakResult insertPaddingBRElement
=
5445 insertPaddingBRElementOrError
.unwrap();
5446 if (!insertPaddingBRElement
.Handled() || !insertingAtCaretPoint
) {
5447 insertPaddingBRElement
.IgnoreCaretPointSuggestion();
5448 return CaretPoint(EditorDOMPoint());
5450 return CaretPoint(insertPaddingBRElement
.UnwrapCaretPoint());
5453 // If we moved content from the right element to the left element, we need to
5454 // maintain padding line break at end of moved content.
5455 if (moveFirstLineResult
.Handled() &&
5456 moveFirstLineResult
.DeleteRangeRef().IsPositioned()) {
5459 NS_FAILED(rv
= EnsureNoFollowingUnnecessaryLineBreak(
5460 moveFirstLineResult
.DeleteRangeRef().EndRef())))) {
5463 Result
<CaretPoint
, nsresult
> caretPointOrError
=
5464 InsertPaddingBRElementIfNeeded(
5465 moveFirstLineResult
.DeleteRangeRef().EndRef());
5466 if (NS_WARN_IF(caretPointOrError
.isErr())) {
5467 return caretPointOrError
.propagateErr();
5469 caretPointOrError
.unwrap().MoveCaretPointTo(
5470 pointToPutCaret
, {SuggestCaret::OnlyIfHasSuggestion
});
5472 // If we only deleted content in the range, we need to maintain padding line
5473 // breaks at both deleted range boundaries.
5474 else if (deleteContentResult
.DeleteRangeRef().IsPositioned()) {
5475 if (!deleteContentResult
.DeleteRangeRef().Collapsed()) {
5478 NS_FAILED(rv
= EnsureNoFollowingUnnecessaryLineBreak(
5479 deleteContentResult
.DeleteRangeRef().EndRef())))) {
5482 // If we deleted blocks following current block, we should not insert
5483 // padding line break after current block when we're handling Backspace.
5484 const bool isFollowingBlockDeletedByBackspace
=
5485 [&]() MOZ_NEVER_INLINE_DEBUG
{
5486 if (putCaretTo
== PutCaretTo::EndOfRange
) {
5489 if (!HTMLEditUtils::RangeIsAcrossStartBlockBoundary(
5490 deleteContentResult
.DeleteRangeRef())) {
5493 WSScanResult nextThing
=
5494 WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary(
5496 deleteContentResult
.DeleteRangeRef().EndRef(),
5497 BlockInlineCheck::UseComputedDisplayOutsideStyle
);
5498 return nextThing
.ReachedBRElement() ||
5499 nextThing
.ReachedPreformattedLineBreak() ||
5500 nextThing
.ReachedHRElement() ||
5501 nextThing
.ReachedBlockBoundary();
5503 if (!isFollowingBlockDeletedByBackspace
) {
5504 Result
<CaretPoint
, nsresult
> caretPointOrError
=
5505 InsertPaddingBRElementIfNeeded(
5506 deleteContentResult
.DeleteRangeRef().EndRef());
5507 if (NS_WARN_IF(caretPointOrError
.isErr())) {
5508 return caretPointOrError
.propagateErr();
5510 caretPointOrError
.unwrap().MoveCaretPointTo(
5511 pointToPutCaret
, {SuggestCaret::OnlyIfHasSuggestion
});
5514 // If we deleted content only after current block, we don't need to
5515 // maintain line breaks at start of the deleted range because nothing has
5516 // been changed from the caret point of view.
5517 if (!maybeDeleteOnlyFollowingContentOfFollowingBlockBoundary
) {
5519 if (NS_WARN_IF(NS_FAILED(
5520 rv
= EnsureNoFollowingUnnecessaryLineBreak(
5521 deleteContentResult
.DeleteRangeRef().StartRef())))) {
5524 Result
<CaretPoint
, nsresult
> caretPointOrError
=
5525 InsertPaddingBRElementIfNeeded(
5526 deleteContentResult
.DeleteRangeRef().StartRef());
5527 if (NS_WARN_IF(caretPointOrError
.isErr())) {
5528 return caretPointOrError
.propagateErr();
5530 caretPointOrError
.unwrap().MoveCaretPointTo(
5531 pointToPutCaret
, {SuggestCaret::OnlyIfHasSuggestion
});
5535 nsresult rv
= aHTMLEditor
.CollapseSelectionTo(pointToPutCaret
);
5536 if (NS_FAILED(rv
)) {
5537 NS_WARNING("EditorBase::CollapseSelectionTo() failed");
5540 if (mMode
== Mode::DeletePrecedingLinesAndContentInRange
||
5541 moveFirstLineResult
.Handled()) {
5542 // If we prefer to use style in the previous line, we should forget previous
5543 // styles since the caret position has all styles which we want to use with
5545 if (backspaceInRightBlock
) {
5546 aHTMLEditor
.TopLevelEditSubActionDataRef().mCachedPendingStyles
->Clear();
5548 // And we don't want to keep extending a link at ex-end of the previous
5550 if (HTMLEditor::GetLinkElement(pointToPutCaret
.GetContainer())) {
5551 aHTMLEditor
.mPendingStylesToApplyToNewContent
5552 ->ClearLinkAndItsSpecifiedStyle();
5555 return EditActionResult::HandledResult();
5558 nsresult
HTMLEditor::AutoDeleteRangesHandler::DeleteUnnecessaryNodes(
5559 HTMLEditor
& aHTMLEditor
, const EditorDOMRange
& aRange
,
5560 const Element
& aEditingHost
) {
5561 MOZ_ASSERT(aHTMLEditor
.IsTopLevelEditSubActionDataAvailable());
5562 MOZ_ASSERT(EditorUtils::IsEditableContent(
5563 *aRange
.StartRef().ContainerAs
<nsIContent
>(), EditorType::HTML
));
5564 MOZ_ASSERT(EditorUtils::IsEditableContent(
5565 *aRange
.EndRef().ContainerAs
<nsIContent
>(), EditorType::HTML
));
5567 EditorDOMRange
range(aRange
);
5569 // If we're handling DnD, this is called to delete dragging item from the
5570 // tree. In this case, we should remove parent blocks if it becomes empty.
5571 if (aHTMLEditor
.GetEditAction() == EditAction::eDrop
||
5572 aHTMLEditor
.GetEditAction() == EditAction::eDeleteByDrag
) {
5573 MOZ_ASSERT(range
.Collapsed() ||
5574 (range
.StartRef().GetContainer()->GetNextSibling() ==
5575 range
.EndRef().GetContainer() &&
5576 range
.StartRef().IsEndOfContainer() &&
5577 range
.EndRef().IsStartOfContainer()));
5578 AutoTrackDOMRange
trackRange(aHTMLEditor
.RangeUpdaterRef(), &range
);
5581 DeleteParentBlocksWithTransactionIfEmpty(aHTMLEditor
, range
.StartRef());
5582 if (NS_FAILED(rv
)) {
5584 "HTMLEditor::DeleteParentBlocksWithTransactionIfEmpty() failed");
5587 aHTMLEditor
.TopLevelEditSubActionDataRef().mDidDeleteEmptyParentBlocks
=
5589 // If we removed parent blocks, Selection should be collapsed at where
5590 // the most ancestor empty block has been.
5591 if (aHTMLEditor
.TopLevelEditSubActionDataRef()
5592 .mDidDeleteEmptyParentBlocks
) {
5597 if (NS_WARN_IF(!range
.IsInContentNodes()) ||
5598 NS_WARN_IF(!EditorUtils::IsEditableContent(
5599 *range
.StartRef().ContainerAs
<nsIContent
>(), EditorType::HTML
)) ||
5600 NS_WARN_IF(!EditorUtils::IsEditableContent(
5601 *range
.EndRef().ContainerAs
<nsIContent
>(), EditorType::HTML
))) {
5602 return NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
;
5605 // We might have left only collapsed white-space in the start/end nodes
5607 AutoTrackDOMRange
trackRange(aHTMLEditor
.RangeUpdaterRef(), &range
);
5609 OwningNonNull
<nsIContent
> startContainer
=
5610 *range
.StartRef().ContainerAs
<nsIContent
>();
5611 OwningNonNull
<nsIContent
> endContainer
=
5612 *range
.EndRef().ContainerAs
<nsIContent
>();
5614 DeleteNodeIfInvisibleAndEditableTextNode(aHTMLEditor
, startContainer
);
5615 if (NS_WARN_IF(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
5616 return NS_ERROR_EDITOR_DESTROYED
;
5618 NS_WARNING_ASSERTION(
5620 "AutoDeleteRangesHandler::DeleteNodeIfInvisibleAndEditableTextNode() "
5621 "failed to remove start node, but ignored");
5622 // If we've not handled the selection end container, and it's still
5623 // editable, let's handle it.
5624 if (!range
.InSameContainer() &&
5625 EditorUtils::IsEditableContent(
5626 *range
.EndRef().ContainerAs
<nsIContent
>(), EditorType::HTML
)) {
5627 rv
= DeleteNodeIfInvisibleAndEditableTextNode(aHTMLEditor
, endContainer
);
5628 if (NS_WARN_IF(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
5629 return NS_ERROR_EDITOR_DESTROYED
;
5631 NS_WARNING_ASSERTION(
5633 "AutoDeleteRangesHandler::DeleteNodeIfInvisibleAndEditableTextNode() "
5634 "failed to remove end node, but ignored");
5638 if (NS_WARN_IF(!range
.IsPositioned())) {
5639 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
);
5642 if (MOZ_LIKELY(range
.EndRef().IsInContentNode())) {
5643 AutoTrackDOMRange
trackRange(aHTMLEditor
.RangeUpdaterRef(), &range
);
5644 nsresult rv
= aHTMLEditor
.EnsureNoFollowingUnnecessaryLineBreak(
5645 range
.EndRef(), aEditingHost
);
5646 if (NS_FAILED(rv
)) {
5647 NS_WARNING("HTMLEditor::EnsureNoFollowingUnnecessaryLineBreak() failed");
5651 if (NS_WARN_IF(!range
.IsPositioned())) {
5652 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
);
5659 HTMLEditor::AutoDeleteRangesHandler::DeleteNodeIfInvisibleAndEditableTextNode(
5660 HTMLEditor
& aHTMLEditor
, nsIContent
& aContent
) {
5661 MOZ_ASSERT(aHTMLEditor
.IsEditActionDataAvailable());
5663 Text
* text
= aContent
.GetAsText();
5668 if (!HTMLEditUtils::IsRemovableFromParentNode(*text
) ||
5669 HTMLEditUtils::IsVisibleTextNode(*text
)) {
5673 nsresult rv
= aHTMLEditor
.DeleteNodeWithTransaction(aContent
);
5674 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
5675 "EditorBase::DeleteNodeWithTransaction() failed");
5680 HTMLEditor::AutoDeleteRangesHandler::DeleteParentBlocksWithTransactionIfEmpty(
5681 HTMLEditor
& aHTMLEditor
, const EditorDOMPoint
& aPoint
) {
5682 MOZ_ASSERT(aPoint
.IsSet());
5683 MOZ_ASSERT(aHTMLEditor
.mPlaceholderBatch
);
5685 // First, check there is visible contents before the point in current block.
5686 RefPtr
<Element
> editingHost
= aHTMLEditor
.ComputeEditingHost();
5687 WSRunScanner
wsScannerForPoint(
5688 editingHost
, aPoint
, BlockInlineCheck::UseComputedDisplayOutsideStyle
);
5689 if (!wsScannerForPoint
.StartsFromCurrentBlockBoundary() &&
5690 !wsScannerForPoint
.StartsFromInlineEditingHostBoundary()) {
5691 // If there is visible node before the point, we shouldn't remove the
5693 return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND
;
5695 if (NS_WARN_IF(!wsScannerForPoint
.GetStartReasonContent()) ||
5696 NS_WARN_IF(!wsScannerForPoint
.GetStartReasonContent()->GetParentNode())) {
5697 return NS_ERROR_FAILURE
;
5699 if (editingHost
== wsScannerForPoint
.GetStartReasonContent()) {
5700 // If we reach editing host, there is no parent blocks which can be removed.
5701 return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND
;
5703 if (HTMLEditUtils::IsTableCellOrCaption(
5704 *wsScannerForPoint
.GetStartReasonContent())) {
5705 // If we reach a <td>, <th> or <caption>, we shouldn't remove it even
5706 // becomes empty because removing such element changes the structure of
5708 return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND
;
5711 // Next, check there is visible contents after the point in current block.
5712 const WSScanResult forwardScanFromPointResult
=
5713 wsScannerForPoint
.ScanInclusiveNextVisibleNodeOrBlockBoundaryFrom(aPoint
);
5714 if (forwardScanFromPointResult
.Failed()) {
5715 NS_WARNING("WSRunScanner::ScanNextVisibleNodeOrBlockBoundaryFrom() failed");
5716 return NS_ERROR_FAILURE
;
5718 if (forwardScanFromPointResult
.ReachedBRElement()) {
5719 // XXX In my understanding, this is odd. The end reason may not be
5720 // same as the reached <br> element because the equality is
5721 // guaranteed only when ReachedCurrentBlockBoundary() returns true.
5722 // However, looks like that this code assumes that
5723 // GetEndReasonContent() returns the (or a) <br> element.
5724 NS_ASSERTION(wsScannerForPoint
.GetEndReasonContent() ==
5725 forwardScanFromPointResult
.BRElementPtr(),
5726 "End reason is not the reached <br> element");
5727 // If the <br> element is visible, we shouldn't remove the parent block.
5728 if (HTMLEditUtils::IsVisibleBRElement(
5729 *wsScannerForPoint
.GetEndReasonContent())) {
5730 return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND
;
5732 if (wsScannerForPoint
.GetEndReasonContent()->GetNextSibling()) {
5733 const WSScanResult scanResult
=
5734 WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary(
5736 EditorRawDOMPoint::After(
5737 *wsScannerForPoint
.GetEndReasonContent()),
5738 BlockInlineCheck::UseComputedDisplayOutsideStyle
);
5739 if (scanResult
.Failed()) {
5740 NS_WARNING("WSRunScanner::ScanNextVisibleNodeOrBlockBoundary() failed");
5741 return NS_ERROR_FAILURE
;
5743 if (!scanResult
.ReachedCurrentBlockBoundary() &&
5744 !scanResult
.ReachedInlineEditingHostBoundary()) {
5745 // If we couldn't reach the block's end after the invisible <br>,
5746 // that means that there is visible content.
5747 return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND
;
5750 } else if (!forwardScanFromPointResult
.ReachedCurrentBlockBoundary() &&
5751 !forwardScanFromPointResult
.ReachedInlineEditingHostBoundary()) {
5752 // If we couldn't reach the block's end, the block has visible content.
5753 return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND
;
5756 // Delete the parent block.
5757 EditorDOMPoint
nextPoint(
5758 wsScannerForPoint
.GetStartReasonContent()->GetParentNode(), 0);
5759 nsresult rv
= aHTMLEditor
.DeleteNodeWithTransaction(
5760 MOZ_KnownLive(*wsScannerForPoint
.GetStartReasonContent()));
5761 if (NS_FAILED(rv
)) {
5762 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
5765 // If we reach editing host, return NS_OK.
5766 if (nextPoint
.GetContainer() == editingHost
) {
5770 // Otherwise, we need to check whether we're still in empty block or not.
5772 // If we have mutation event listeners, the next point is now outside of
5773 // editing host or editing hos has been changed.
5774 if (aHTMLEditor
.MayHaveMutationEventListeners(
5775 NS_EVENT_BITS_MUTATION_NODEREMOVED
|
5776 NS_EVENT_BITS_MUTATION_NODEREMOVEDFROMDOCUMENT
|
5777 NS_EVENT_BITS_MUTATION_SUBTREEMODIFIED
)) {
5778 Element
* newEditingHost
= aHTMLEditor
.ComputeEditingHost();
5779 if (NS_WARN_IF(!newEditingHost
) ||
5780 NS_WARN_IF(newEditingHost
!= editingHost
)) {
5781 return NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
;
5783 if (NS_WARN_IF(!EditorUtils::IsDescendantOf(*nextPoint
.GetContainer(),
5784 *newEditingHost
))) {
5785 return NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
;
5789 rv
= DeleteParentBlocksWithTransactionIfEmpty(aHTMLEditor
, nextPoint
);
5790 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
5791 "AutoDeleteRangesHandler::"
5792 "DeleteParentBlocksWithTransactionIfEmpty() failed");
5797 HTMLEditor::AutoDeleteRangesHandler::ComputeRangeToDeleteRangeWithTransaction(
5798 const HTMLEditor
& aHTMLEditor
, nsIEditor::EDirection aDirectionAndAmount
,
5799 nsRange
& aRangeToDelete
, const Element
& aEditingHost
) const {
5800 MOZ_ASSERT(aHTMLEditor
.IsEditActionDataAvailable());
5802 const EditorBase::HowToHandleCollapsedRange howToHandleCollapsedRange
=
5803 EditorBase::HowToHandleCollapsedRangeFor(aDirectionAndAmount
);
5804 if (MOZ_UNLIKELY(aRangeToDelete
.Collapsed() &&
5805 howToHandleCollapsedRange
==
5806 EditorBase::HowToHandleCollapsedRange::Ignore
)) {
5807 return NS_SUCCESS_DOM_NO_OPERATION
;
5810 // If it's not collapsed, `DeleteRangeTransaction::Create()` will be called
5811 // with it and `DeleteRangeTransaction` won't modify the range.
5812 if (!aRangeToDelete
.Collapsed()) {
5816 const auto ExtendRangeToSelectCharacterForward
=
5817 [](nsRange
& aRange
, const EditorRawDOMPointInText
& aCaretPoint
) -> void {
5818 const nsTextFragment
& textFragment
=
5819 aCaretPoint
.ContainerAs
<Text
>()->TextFragment();
5820 if (!textFragment
.GetLength()) {
5823 if (textFragment
.IsHighSurrogateFollowedByLowSurrogateAt(
5824 aCaretPoint
.Offset())) {
5825 DebugOnly
<nsresult
> rvIgnored
= aRange
.SetStartAndEnd(
5826 aCaretPoint
.ContainerAs
<Text
>(), aCaretPoint
.Offset(),
5827 aCaretPoint
.ContainerAs
<Text
>(), aCaretPoint
.Offset() + 2);
5828 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored
),
5829 "nsRange::SetStartAndEnd() failed");
5832 DebugOnly
<nsresult
> rvIgnored
= aRange
.SetStartAndEnd(
5833 aCaretPoint
.ContainerAs
<Text
>(), aCaretPoint
.Offset(),
5834 aCaretPoint
.ContainerAs
<Text
>(), aCaretPoint
.Offset() + 1);
5835 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored
),
5836 "nsRange::SetStartAndEnd() failed");
5838 const auto ExtendRangeToSelectCharacterBackward
=
5839 [](nsRange
& aRange
, const EditorRawDOMPointInText
& aCaretPoint
) -> void {
5840 if (aCaretPoint
.IsStartOfContainer()) {
5843 const nsTextFragment
& textFragment
=
5844 aCaretPoint
.ContainerAs
<Text
>()->TextFragment();
5845 if (!textFragment
.GetLength()) {
5848 if (textFragment
.IsLowSurrogateFollowingHighSurrogateAt(
5849 aCaretPoint
.Offset() - 1)) {
5850 DebugOnly
<nsresult
> rvIgnored
= aRange
.SetStartAndEnd(
5851 aCaretPoint
.ContainerAs
<Text
>(), aCaretPoint
.Offset() - 2,
5852 aCaretPoint
.ContainerAs
<Text
>(), aCaretPoint
.Offset());
5853 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored
),
5854 "nsRange::SetStartAndEnd() failed");
5857 DebugOnly
<nsresult
> rvIgnored
= aRange
.SetStartAndEnd(
5858 aCaretPoint
.ContainerAs
<Text
>(), aCaretPoint
.Offset() - 1,
5859 aCaretPoint
.ContainerAs
<Text
>(), aCaretPoint
.Offset());
5860 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored
),
5861 "nsRange::SetStartAndEnd() failed");
5864 // In the other cases, `EditorBase::CreateTransactionForCollapsedRange()`
5865 // will handle the collapsed range.
5866 EditorRawDOMPoint
caretPoint(aRangeToDelete
.StartRef());
5867 if (howToHandleCollapsedRange
==
5868 EditorBase::HowToHandleCollapsedRange::ExtendBackward
&&
5869 caretPoint
.IsStartOfContainer()) {
5870 nsIContent
* previousEditableContent
= HTMLEditUtils::GetPreviousContent(
5871 *caretPoint
.GetContainer(), {WalkTreeOption::IgnoreNonEditableNode
},
5872 BlockInlineCheck::Unused
, &aEditingHost
);
5873 if (!previousEditableContent
) {
5876 if (!previousEditableContent
->IsText()) {
5877 IgnoredErrorResult ignoredError
;
5878 aRangeToDelete
.SelectNode(*previousEditableContent
, ignoredError
);
5879 NS_WARNING_ASSERTION(!ignoredError
.Failed(),
5880 "nsRange::SelectNode() failed");
5884 ExtendRangeToSelectCharacterBackward(
5886 EditorRawDOMPointInText::AtEndOf(*previousEditableContent
->AsText()));
5890 if (howToHandleCollapsedRange
==
5891 EditorBase::HowToHandleCollapsedRange::ExtendForward
&&
5892 caretPoint
.IsEndOfContainer()) {
5893 nsIContent
* nextEditableContent
= HTMLEditUtils::GetNextContent(
5894 *caretPoint
.GetContainer(), {WalkTreeOption::IgnoreNonEditableNode
},
5895 BlockInlineCheck::Unused
, &aEditingHost
);
5896 if (!nextEditableContent
) {
5900 if (!nextEditableContent
->IsText()) {
5901 IgnoredErrorResult ignoredError
;
5902 aRangeToDelete
.SelectNode(*nextEditableContent
, ignoredError
);
5903 NS_WARNING_ASSERTION(!ignoredError
.Failed(),
5904 "nsRange::SelectNode() failed");
5908 ExtendRangeToSelectCharacterForward(
5910 EditorRawDOMPointInText(nextEditableContent
->AsText(), 0));
5914 if (caretPoint
.IsInTextNode()) {
5915 if (howToHandleCollapsedRange
==
5916 EditorBase::HowToHandleCollapsedRange::ExtendBackward
) {
5917 ExtendRangeToSelectCharacterBackward(
5919 EditorRawDOMPointInText(caretPoint
.ContainerAs
<Text
>(),
5920 caretPoint
.Offset()));
5923 ExtendRangeToSelectCharacterForward(
5924 aRangeToDelete
, EditorRawDOMPointInText(caretPoint
.ContainerAs
<Text
>(),
5925 caretPoint
.Offset()));
5929 nsIContent
* editableContent
=
5930 howToHandleCollapsedRange
==
5931 EditorBase::HowToHandleCollapsedRange::ExtendBackward
5932 ? HTMLEditUtils::GetPreviousContent(
5933 caretPoint
, {WalkTreeOption::IgnoreNonEditableNode
},
5934 BlockInlineCheck::Unused
, &aEditingHost
)
5935 : HTMLEditUtils::GetNextContent(
5936 caretPoint
, {WalkTreeOption::IgnoreNonEditableNode
},
5937 BlockInlineCheck::Unused
, &aEditingHost
);
5938 if (!editableContent
) {
5941 while (editableContent
&& editableContent
->IsCharacterData() &&
5942 !editableContent
->Length()) {
5944 howToHandleCollapsedRange
==
5945 EditorBase::HowToHandleCollapsedRange::ExtendBackward
5946 ? HTMLEditUtils::GetPreviousContent(
5947 *editableContent
, {WalkTreeOption::IgnoreNonEditableNode
},
5948 BlockInlineCheck::Unused
, &aEditingHost
)
5949 : HTMLEditUtils::GetNextContent(
5950 *editableContent
, {WalkTreeOption::IgnoreNonEditableNode
},
5951 BlockInlineCheck::Unused
, &aEditingHost
);
5953 if (!editableContent
) {
5957 if (!editableContent
->IsText()) {
5958 IgnoredErrorResult ignoredError
;
5959 aRangeToDelete
.SelectNode(*editableContent
, ignoredError
);
5960 NS_WARNING_ASSERTION(!ignoredError
.Failed(),
5961 "nsRange::SelectNode() failed, but ignored");
5965 if (howToHandleCollapsedRange
==
5966 EditorBase::HowToHandleCollapsedRange::ExtendBackward
) {
5967 ExtendRangeToSelectCharacterBackward(
5969 EditorRawDOMPointInText::AtEndOf(*editableContent
->AsText()));
5972 ExtendRangeToSelectCharacterForward(
5973 aRangeToDelete
, EditorRawDOMPointInText(editableContent
->AsText(), 0));
5978 template <typename EditorDOMPointType
>
5979 Result
<CaretPoint
, nsresult
> HTMLEditor::DeleteTextAndTextNodesWithTransaction(
5980 const EditorDOMPointType
& aStartPoint
, const EditorDOMPointType
& aEndPoint
,
5981 TreatEmptyTextNodes aTreatEmptyTextNodes
) {
5982 if (NS_WARN_IF(!aStartPoint
.IsSet()) || NS_WARN_IF(!aEndPoint
.IsSet())) {
5983 return Err(NS_ERROR_INVALID_ARG
);
5986 // MOOSE: this routine needs to be modified to preserve the integrity of the
5989 if (aStartPoint
== aEndPoint
) {
5990 // Nothing to delete
5991 return CaretPoint(EditorDOMPoint());
5994 RefPtr
<Element
> editingHost
= ComputeEditingHost();
5995 auto DeleteEmptyContentNodeWithTransaction
=
5996 [this, &aTreatEmptyTextNodes
, &editingHost
](nsIContent
& aContent
)
5997 MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION
-> nsresult
{
5998 OwningNonNull
<nsIContent
> nodeToRemove
= aContent
;
5999 if (aTreatEmptyTextNodes
==
6000 TreatEmptyTextNodes::RemoveAllEmptyInlineAncestors
) {
6001 Element
* emptyParentElementToRemove
=
6002 HTMLEditUtils::GetMostDistantAncestorEditableEmptyInlineElement(
6003 nodeToRemove
, BlockInlineCheck::UseComputedDisplayOutsideStyle
,
6005 if (emptyParentElementToRemove
) {
6006 nodeToRemove
= *emptyParentElementToRemove
;
6009 nsresult rv
= DeleteNodeWithTransaction(nodeToRemove
);
6010 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
6011 "EditorBase::DeleteNodeWithTransaction() failed");
6015 if (aStartPoint
.GetContainer() == aEndPoint
.GetContainer() &&
6016 aStartPoint
.IsInTextNode()) {
6017 if (aTreatEmptyTextNodes
!=
6018 TreatEmptyTextNodes::KeepIfContainerOfRangeBoundaries
&&
6019 aStartPoint
.IsStartOfContainer() && aEndPoint
.IsEndOfContainer()) {
6020 nsresult rv
= DeleteEmptyContentNodeWithTransaction(
6021 MOZ_KnownLive(*aStartPoint
.template ContainerAs
<Text
>()));
6022 if (NS_FAILED(rv
)) {
6023 NS_WARNING("deleteEmptyContentNodeWithTransaction() failed");
6026 return CaretPoint(EditorDOMPoint());
6028 RefPtr
<Text
> textNode
= aStartPoint
.template ContainerAs
<Text
>();
6029 Result
<CaretPoint
, nsresult
> caretPointOrError
=
6030 DeleteTextWithTransaction(*textNode
, aStartPoint
.Offset(),
6031 aEndPoint
.Offset() - aStartPoint
.Offset());
6032 NS_WARNING_ASSERTION(caretPointOrError
.isOk(),
6033 "HTMLEditor::DeleteTextWithTransaction() failed");
6034 return caretPointOrError
;
6037 RefPtr
<nsRange
> range
=
6038 nsRange::Create(aStartPoint
.ToRawRangeBoundary(),
6039 aEndPoint
.ToRawRangeBoundary(), IgnoreErrors());
6041 NS_WARNING("nsRange::Create() failed");
6042 return Err(NS_ERROR_FAILURE
);
6045 // Collect editable text nodes in the given range.
6046 AutoTArray
<OwningNonNull
<Text
>, 16> arrayOfTextNodes
;
6048 if (NS_FAILED(iter
.Init(*range
))) {
6049 return CaretPoint(EditorDOMPoint()); // Nothing to delete in the range.
6051 iter
.AppendNodesToArray(
6052 +[](nsINode
& aNode
, void*) {
6053 MOZ_ASSERT(aNode
.IsText());
6054 return HTMLEditUtils::IsSimplyEditableNode(aNode
);
6057 EditorDOMPoint pointToPutCaret
;
6058 for (OwningNonNull
<Text
>& textNode
: arrayOfTextNodes
) {
6059 if (textNode
== aStartPoint
.GetContainer()) {
6060 if (aStartPoint
.IsEndOfContainer()) {
6063 if (aStartPoint
.IsStartOfContainer() &&
6064 aTreatEmptyTextNodes
!=
6065 TreatEmptyTextNodes::KeepIfContainerOfRangeBoundaries
) {
6066 AutoTrackDOMPoint
trackPointToPutCaret(RangeUpdaterRef(),
6068 nsresult rv
= DeleteEmptyContentNodeWithTransaction(
6069 MOZ_KnownLive(*aStartPoint
.template ContainerAs
<Text
>()));
6070 if (NS_FAILED(rv
)) {
6071 NS_WARNING("DeleteEmptyContentNodeWithTransaction() failed");
6076 AutoTrackDOMPoint
trackPointToPutCaret(RangeUpdaterRef(),
6078 Result
<CaretPoint
, nsresult
> caretPointOrError
=
6079 DeleteTextWithTransaction(MOZ_KnownLive(textNode
),
6080 aStartPoint
.Offset(),
6081 textNode
->Length() - aStartPoint
.Offset());
6082 if (MOZ_UNLIKELY(caretPointOrError
.isErr())) {
6083 NS_WARNING("HTMLEditor::DeleteTextWithTransaction() failed");
6084 return caretPointOrError
;
6086 trackPointToPutCaret
.FlushAndStopTracking();
6087 caretPointOrError
.unwrap().MoveCaretPointTo(
6088 pointToPutCaret
, {SuggestCaret::OnlyIfHasSuggestion
});
6092 if (textNode
== aEndPoint
.GetContainer()) {
6093 if (aEndPoint
.IsStartOfContainer()) {
6096 if (aEndPoint
.IsEndOfContainer() &&
6097 aTreatEmptyTextNodes
!=
6098 TreatEmptyTextNodes::KeepIfContainerOfRangeBoundaries
) {
6099 AutoTrackDOMPoint
trackPointToPutCaret(RangeUpdaterRef(),
6101 nsresult rv
= DeleteEmptyContentNodeWithTransaction(
6102 MOZ_KnownLive(*aEndPoint
.template ContainerAs
<Text
>()));
6103 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
6104 "DeleteEmptyContentNodeWithTransaction() failed");
6107 AutoTrackDOMPoint
trackPointToPutCaret(RangeUpdaterRef(),
6109 Result
<CaretPoint
, nsresult
> caretPointOrError
=
6110 DeleteTextWithTransaction(MOZ_KnownLive(textNode
), 0,
6111 aEndPoint
.Offset());
6112 if (MOZ_UNLIKELY(caretPointOrError
.isErr())) {
6113 NS_WARNING("HTMLEditor::DeleteTextWithTransaction() failed");
6114 return caretPointOrError
;
6116 trackPointToPutCaret
.FlushAndStopTracking();
6117 caretPointOrError
.unwrap().MoveCaretPointTo(
6118 pointToPutCaret
, {SuggestCaret::OnlyIfHasSuggestion
});
6119 return CaretPoint(pointToPutCaret
);
6123 DeleteEmptyContentNodeWithTransaction(MOZ_KnownLive(textNode
));
6124 if (NS_FAILED(rv
)) {
6125 NS_WARNING("DeleteEmptyContentNodeWithTransaction() failed");
6130 return CaretPoint(pointToPutCaret
);
6133 Result
<EditorDOMPoint
, nsresult
> HTMLEditor::AutoDeleteRangesHandler::
6134 AutoBlockElementsJoiner::JoinNodesDeepWithTransaction(
6135 HTMLEditor
& aHTMLEditor
, nsIContent
& aLeftContent
,
6136 nsIContent
& aRightContent
) {
6137 // While the rightmost children and their descendants of the left node match
6138 // the leftmost children and their descendants of the right node, join them
6141 nsCOMPtr
<nsIContent
> leftContentToJoin
= &aLeftContent
;
6142 nsCOMPtr
<nsIContent
> rightContentToJoin
= &aRightContent
;
6143 nsCOMPtr
<nsINode
> parentNode
= aRightContent
.GetParentNode();
6146 while (leftContentToJoin
&& rightContentToJoin
&& parentNode
&&
6147 HTMLEditUtils::CanContentsBeJoined(*leftContentToJoin
,
6148 *rightContentToJoin
)) {
6150 Result
<JoinNodesResult
, nsresult
> joinNodesResult
=
6151 aHTMLEditor
.JoinNodesWithTransaction(*leftContentToJoin
,
6152 *rightContentToJoin
);
6153 if (MOZ_UNLIKELY(joinNodesResult
.isErr())) {
6154 NS_WARNING("HTMLEditor::JoinNodesWithTransaction() failed");
6155 return joinNodesResult
.propagateErr();
6158 ret
= joinNodesResult
.inspect().AtJoinedPoint
<EditorDOMPoint
>();
6159 if (NS_WARN_IF(!ret
.IsSet())) {
6160 return Err(NS_ERROR_FAILURE
);
6163 if (parentNode
->IsText()) {
6164 // We've joined all the way down to text nodes, we're done!
6168 // Get new left and right nodes, and begin anew
6169 rightContentToJoin
= ret
.GetCurrentChildAtOffset();
6170 if (rightContentToJoin
) {
6171 leftContentToJoin
= rightContentToJoin
->GetPreviousSibling();
6173 leftContentToJoin
= nullptr;
6176 // Skip over non-editable nodes
6177 while (leftContentToJoin
&& !EditorUtils::IsEditableContent(
6178 *leftContentToJoin
, EditorType::HTML
)) {
6179 leftContentToJoin
= leftContentToJoin
->GetPreviousSibling();
6181 if (!leftContentToJoin
) {
6185 while (rightContentToJoin
&& !EditorUtils::IsEditableContent(
6186 *rightContentToJoin
, EditorType::HTML
)) {
6187 rightContentToJoin
= rightContentToJoin
->GetNextSibling();
6189 if (!rightContentToJoin
) {
6195 NS_WARNING("HTMLEditor::JoinNodesDeepWithTransaction() joined no contents");
6196 return Err(NS_ERROR_FAILURE
);
6201 Result
<bool, nsresult
> HTMLEditor::AutoDeleteRangesHandler::
6202 AutoBlockElementsJoiner::AutoInclusiveAncestorBlockElementsJoiner::Prepare(
6203 const HTMLEditor
& aHTMLEditor
, const Element
& aEditingHost
) {
6204 mLeftBlockElement
= HTMLEditUtils::GetInclusiveAncestorElement(
6205 mInclusiveDescendantOfLeftBlockElement
,
6206 HTMLEditUtils::ClosestEditableBlockElementExceptHRElement
,
6207 BlockInlineCheck::UseComputedDisplayOutsideStyle
);
6208 mRightBlockElement
= HTMLEditUtils::GetInclusiveAncestorElement(
6209 mInclusiveDescendantOfRightBlockElement
,
6210 HTMLEditUtils::ClosestEditableBlockElementExceptHRElement
,
6211 BlockInlineCheck::UseComputedDisplayOutsideStyle
);
6213 if (NS_WARN_IF(!IsSet())) {
6214 mCanJoinBlocks
= false;
6215 return Err(NS_ERROR_UNEXPECTED
);
6218 // Don't join the blocks if both of them are basic structure of the HTML
6219 // document (Note that `<body>` can be joined with its children).
6220 if (mLeftBlockElement
->IsAnyOfHTMLElements(nsGkAtoms::html
, nsGkAtoms::head
,
6222 mRightBlockElement
->IsAnyOfHTMLElements(nsGkAtoms::html
, nsGkAtoms::head
,
6224 mCanJoinBlocks
= false;
6228 if (HTMLEditUtils::IsAnyTableElement(mLeftBlockElement
) ||
6229 HTMLEditUtils::IsAnyTableElement(mRightBlockElement
)) {
6230 // Do not try to merge table elements, cancel the deletion.
6231 mCanJoinBlocks
= false;
6235 // Bail if both blocks the same
6236 if (IsSameBlockElement()) {
6237 mCanJoinBlocks
= true; // XXX Anyway, Run() will ingore this case.
6238 mFallbackToDeleteLeafContent
= true;
6242 // Joining a list item to its parent is a NOP.
6243 if (HTMLEditUtils::IsAnyListElement(mLeftBlockElement
) &&
6244 HTMLEditUtils::IsListItem(mRightBlockElement
) &&
6245 mRightBlockElement
->GetParentNode() == mLeftBlockElement
) {
6246 mCanJoinBlocks
= false;
6250 // Special rule here: if we are trying to join list items, and they are in
6251 // different lists, join the lists instead.
6252 if (HTMLEditUtils::IsListItem(mLeftBlockElement
) &&
6253 HTMLEditUtils::IsListItem(mRightBlockElement
)) {
6254 // XXX leftListElement and/or rightListElement may be not list elements.
6255 Element
* leftListElement
= mLeftBlockElement
->GetParentElement();
6256 Element
* rightListElement
= mRightBlockElement
->GetParentElement();
6257 EditorDOMPoint atChildInBlock
;
6258 if (leftListElement
&& rightListElement
&&
6259 leftListElement
!= rightListElement
&&
6260 !EditorUtils::IsDescendantOf(*leftListElement
, *mRightBlockElement
,
6262 !EditorUtils::IsDescendantOf(*rightListElement
, *mLeftBlockElement
,
6264 // There are some special complications if the lists are descendants of
6265 // the other lists' items. Note that it is okay for them to be
6266 // descendants of the other lists themselves, which is the usual case for
6267 // sublists in our implementation.
6268 MOZ_DIAGNOSTIC_ASSERT(!atChildInBlock
.IsSet());
6269 mLeftBlockElement
= leftListElement
;
6270 mRightBlockElement
= rightListElement
;
6271 mNewListElementTagNameOfRightListElement
=
6272 Some(leftListElement
->NodeInfo()->NameAtom());
6276 if (!EditorUtils::IsDescendantOf(*mLeftBlockElement
, *mRightBlockElement
,
6277 &mPointContainingTheOtherBlockElement
)) {
6278 Unused
<< EditorUtils::IsDescendantOf(
6279 *mRightBlockElement
, *mLeftBlockElement
,
6280 &mPointContainingTheOtherBlockElement
);
6283 if (mPointContainingTheOtherBlockElement
.GetContainer() ==
6284 mRightBlockElement
) {
6285 mPrecedingInvisibleBRElement
=
6286 WSRunScanner::GetPrecedingBRElementUnlessVisibleContentFound(
6287 aHTMLEditor
.ComputeEditingHost(),
6288 EditorDOMPoint::AtEndOf(mLeftBlockElement
),
6289 BlockInlineCheck::UseComputedDisplayOutsideStyle
);
6290 // `WhiteSpaceVisibilityKeeper::
6291 // MergeFirstLineOfRightBlockElementIntoDescendantLeftBlockElement()`
6292 // returns ignored when:
6293 // - No preceding invisible `<br>` element and
6294 // - mNewListElementTagNameOfRightListElement is nothing and
6295 // - There is no content to move from right block element.
6296 if (!mPrecedingInvisibleBRElement
) {
6297 if (CanMergeLeftAndRightBlockElements()) {
6298 // Always marked as handled in this case.
6299 mFallbackToDeleteLeafContent
= false;
6301 // Marked as handled only when it actually moves a content node.
6302 Result
<bool, nsresult
> firstLineHasContent
=
6303 AutoMoveOneLineHandler::CanMoveOrDeleteSomethingInLine(
6304 mPointContainingTheOtherBlockElement
6305 .NextPoint
<EditorDOMPoint
>(),
6307 mFallbackToDeleteLeafContent
=
6308 firstLineHasContent
.isOk() && !firstLineHasContent
.inspect();
6311 // Marked as handled when deleting the invisible `<br>` element.
6312 mFallbackToDeleteLeafContent
= false;
6314 } else if (mPointContainingTheOtherBlockElement
.GetContainer() ==
6315 mLeftBlockElement
) {
6316 mPrecedingInvisibleBRElement
=
6317 WSRunScanner::GetPrecedingBRElementUnlessVisibleContentFound(
6318 aHTMLEditor
.ComputeEditingHost(),
6319 mPointContainingTheOtherBlockElement
,
6320 BlockInlineCheck::UseComputedDisplayOutsideStyle
);
6321 // `WhiteSpaceVisibilityKeeper::
6322 // MergeFirstLineOfRightBlockElementIntoAncestorLeftBlockElement()`
6323 // returns ignored when:
6324 // - No preceding invisible `<br>` element and
6325 // - mNewListElementTagNameOfRightListElement is some and
6326 // - The right block element has no children
6328 // - No preceding invisible `<br>` element and
6329 // - mNewListElementTagNameOfRightListElement is nothing and
6330 // - There is no content to move from right block element.
6331 if (!mPrecedingInvisibleBRElement
) {
6332 if (CanMergeLeftAndRightBlockElements()) {
6333 // Marked as handled only when it actualy moves a content node.
6334 Result
<bool, nsresult
> rightBlockHasContent
=
6335 aHTMLEditor
.CanMoveChildren(*mRightBlockElement
,
6336 *mLeftBlockElement
);
6337 mFallbackToDeleteLeafContent
=
6338 rightBlockHasContent
.isOk() && !rightBlockHasContent
.inspect();
6340 // Marked as handled only when it actually moves a content node.
6341 Result
<bool, nsresult
> firstLineHasContent
=
6342 AutoMoveOneLineHandler::CanMoveOrDeleteSomethingInLine(
6343 EditorDOMPoint(mRightBlockElement
, 0u), aEditingHost
);
6344 mFallbackToDeleteLeafContent
=
6345 firstLineHasContent
.isOk() && !firstLineHasContent
.inspect();
6348 // Marked as handled when deleting the invisible `<br>` element.
6349 mFallbackToDeleteLeafContent
= false;
6352 mPrecedingInvisibleBRElement
=
6353 WSRunScanner::GetPrecedingBRElementUnlessVisibleContentFound(
6354 aHTMLEditor
.ComputeEditingHost(),
6355 EditorDOMPoint::AtEndOf(mLeftBlockElement
),
6356 BlockInlineCheck::UseComputedDisplayOutsideStyle
);
6357 // `WhiteSpaceVisibilityKeeper::
6358 // MergeFirstLineOfRightBlockElementIntoLeftBlockElement()` always
6359 // return "handled".
6360 mFallbackToDeleteLeafContent
= false;
6363 mCanJoinBlocks
= true;
6367 nsresult
HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
6368 AutoInclusiveAncestorBlockElementsJoiner::ComputeRangeToDelete(
6369 const HTMLEditor
& aHTMLEditor
, const EditorDOMPoint
& aCaretPoint
,
6370 nsRange
& aRangeToDelete
) const {
6371 MOZ_ASSERT(mLeftBlockElement
);
6372 MOZ_ASSERT(mRightBlockElement
);
6374 if (IsSameBlockElement()) {
6375 if (!aCaretPoint
.IsSet()) {
6376 return NS_OK
; // The ranges are not collapsed, keep them as-is.
6378 nsresult rv
= aRangeToDelete
.CollapseTo(aCaretPoint
.ToRawRangeBoundary());
6379 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
), "nsRange::CollapseTo() failed");
6383 EditorDOMPoint pointContainingTheOtherBlock
;
6384 if (!EditorUtils::IsDescendantOf(*mLeftBlockElement
, *mRightBlockElement
,
6385 &pointContainingTheOtherBlock
)) {
6386 Unused
<< EditorUtils::IsDescendantOf(
6387 *mRightBlockElement
, *mLeftBlockElement
, &pointContainingTheOtherBlock
);
6389 EditorDOMRange range
=
6390 WSRunScanner::GetRangeForDeletingBlockElementBoundaries(
6391 aHTMLEditor
, *mLeftBlockElement
, *mRightBlockElement
,
6392 pointContainingTheOtherBlock
);
6393 if (!range
.IsPositioned()) {
6395 "WSRunScanner::GetRangeForDeletingBlockElementBoundaries() failed");
6396 return NS_ERROR_FAILURE
;
6398 if (!aCaretPoint
.IsSet()) {
6399 // Don't shrink the original range.
6400 bool noNeedToChangeStart
= false;
6401 const EditorDOMPoint
atStart(aRangeToDelete
.StartRef());
6402 if (atStart
.IsBefore(range
.StartRef())) {
6403 // If the range starts from end of a container, and computed block
6404 // boundaries range starts from an invisible `<br>` element, we
6405 // may need to shrink the range.
6406 Element
* editingHost
= aHTMLEditor
.ComputeEditingHost();
6407 NS_WARNING_ASSERTION(editingHost
, "There was no editing host");
6408 nsIContent
* nextContent
=
6409 atStart
.IsEndOfContainer() && range
.StartRef().GetChild() &&
6410 HTMLEditUtils::IsInvisibleBRElement(
6411 *range
.StartRef().GetChild())
6412 ? HTMLEditUtils::GetNextContent(
6413 *atStart
.ContainerAs
<nsIContent
>(),
6414 {WalkTreeOption::IgnoreDataNodeExceptText
,
6415 WalkTreeOption::StopAtBlockBoundary
},
6416 BlockInlineCheck::UseComputedDisplayOutsideStyle
,
6419 if (!nextContent
|| nextContent
!= range
.StartRef().GetChild()) {
6420 noNeedToChangeStart
= true;
6421 range
.SetStart(EditorRawDOMPoint(aRangeToDelete
.StartRef()));
6424 if (range
.EndRef().IsBefore(EditorRawDOMPoint(aRangeToDelete
.EndRef()))) {
6425 if (noNeedToChangeStart
) {
6426 return NS_OK
; // We don't need to modify the range.
6428 range
.SetEnd(EditorRawDOMPoint(aRangeToDelete
.EndRef()));
6432 aRangeToDelete
.SetStartAndEnd(range
.StartRef().ToRawRangeBoundary(),
6433 range
.EndRef().ToRawRangeBoundary());
6434 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
6435 "AutoClonedRangeArray::SetStartAndEnd() failed");
6439 Result
<DeleteRangeResult
, nsresult
> HTMLEditor::AutoDeleteRangesHandler::
6440 AutoBlockElementsJoiner::AutoInclusiveAncestorBlockElementsJoiner::Run(
6441 HTMLEditor
& aHTMLEditor
, const Element
& aEditingHost
) {
6442 MOZ_ASSERT(aHTMLEditor
.IsEditActionDataAvailable());
6443 MOZ_ASSERT(mLeftBlockElement
);
6444 MOZ_ASSERT(mRightBlockElement
);
6446 if (IsSameBlockElement() || !mCanJoinBlocks
) {
6447 return DeleteRangeResult::IgnoredResult();
6450 const auto ConvertMoveNodeResultToDeleteRangeResult
=
6451 [](const EditorDOMPoint
& aStartOfRightContent
,
6452 MoveNodeResult
&& aMoveNodeResult
, const Element
& aEditingHost
)
6453 MOZ_NEVER_INLINE_DEBUG MOZ_CAN_RUN_SCRIPT
6454 -> Result
<DeleteRangeResult
, nsresult
> {
6455 aMoveNodeResult
.IgnoreCaretPointSuggestion();
6456 if (MOZ_UNLIKELY(aMoveNodeResult
.Ignored())) {
6457 return DeleteRangeResult::IgnoredResult();
6459 EditorDOMRange movedLineRange
= aMoveNodeResult
.UnwrapMovedContentRange();
6460 EditorDOMPoint maybeDeepStartOfRightContent
;
6461 if (MOZ_LIKELY(movedLineRange
.IsPositioned())) {
6462 if (const Element
* const firstMovedElement
=
6463 movedLineRange
.StartRef().GetChildAs
<Element
>()) {
6464 maybeDeepStartOfRightContent
=
6465 HTMLEditUtils::GetDeepestEditableStartPointOf
<EditorDOMPoint
>(
6466 *firstMovedElement
);
6468 maybeDeepStartOfRightContent
= movedLineRange
.StartRef();
6471 maybeDeepStartOfRightContent
= aStartOfRightContent
;
6474 !maybeDeepStartOfRightContent
.IsSetAndValidInComposedDoc())) {
6475 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
);
6477 // We should put caret to end of preceding text node if there is.
6478 // Then, users can type text into it like the other browsers.
6479 auto pointToPutCaret
= [&]() -> EditorDOMPoint
{
6480 WSRunScanner
scanner(&aEditingHost
, maybeDeepStartOfRightContent
,
6481 BlockInlineCheck::UseComputedDisplayStyle
);
6482 const WSScanResult maybePreviousText
=
6483 scanner
.ScanPreviousVisibleNodeOrBlockBoundaryFrom(
6484 maybeDeepStartOfRightContent
);
6485 if (maybePreviousText
.IsContentEditable() &&
6486 maybePreviousText
.InVisibleOrCollapsibleCharacters()) {
6487 return maybePreviousText
.PointAfterReachedContent
<EditorDOMPoint
>();
6489 return maybeDeepStartOfRightContent
;
6491 return DeleteRangeResult(std::move(movedLineRange
),
6492 std::move(pointToPutCaret
));
6495 // If the left block element is in the right block element, move the hard
6496 // line including the right block element to end of the left block.
6497 // However, if we are merging list elements, we don't join them.
6498 if (mPointContainingTheOtherBlockElement
.GetContainer() ==
6499 mRightBlockElement
) {
6500 EditorDOMPoint startOfRightContent
=
6501 mPointContainingTheOtherBlockElement
.NextPoint();
6502 if (const Element
* const element
=
6503 startOfRightContent
.GetChildAs
<Element
>()) {
6504 startOfRightContent
=
6505 HTMLEditUtils::GetDeepestEditableStartPointOf
<EditorDOMPoint
>(
6508 AutoTrackDOMPoint
trackStartOfRightBlock(aHTMLEditor
.RangeUpdaterRef(),
6509 &startOfRightContent
);
6510 Result
<MoveNodeResult
, nsresult
> moveFirstLineResult
=
6511 WhiteSpaceVisibilityKeeper::
6512 MergeFirstLineOfRightBlockElementIntoDescendantLeftBlockElement(
6513 aHTMLEditor
, MOZ_KnownLive(*mLeftBlockElement
),
6514 MOZ_KnownLive(*mRightBlockElement
),
6515 mPointContainingTheOtherBlockElement
,
6516 mNewListElementTagNameOfRightListElement
,
6517 MOZ_KnownLive(mPrecedingInvisibleBRElement
), aEditingHost
);
6518 if (MOZ_UNLIKELY(moveFirstLineResult
.isErr())) {
6520 "WhiteSpaceVisibilityKeeper::"
6521 "MergeFirstLineOfRightBlockElementIntoDescendantLeftBlockElement() "
6523 return moveFirstLineResult
.propagateErr();
6526 trackStartOfRightBlock
.FlushAndStopTracking();
6527 return ConvertMoveNodeResultToDeleteRangeResult(
6528 startOfRightContent
, moveFirstLineResult
.unwrap(), aEditingHost
);
6531 // If the right block element is in the left block element:
6532 // - move list item elements in the right block element to where the left
6534 // - or first hard line in the right block element to where:
6535 // - the left block element is.
6536 // - or the given left content in the left block is.
6537 if (mPointContainingTheOtherBlockElement
.GetContainer() ==
6538 mLeftBlockElement
) {
6539 EditorDOMPoint startOfRightContent
=
6540 HTMLEditUtils::GetDeepestEditableStartPointOf
<EditorDOMPoint
>(
6541 *mRightBlockElement
);
6542 AutoTrackDOMPoint
trackStartOfRightBlock(aHTMLEditor
.RangeUpdaterRef(),
6543 &startOfRightContent
);
6544 Result
<MoveNodeResult
, nsresult
> moveFirstLineResult
=
6545 WhiteSpaceVisibilityKeeper::
6546 MergeFirstLineOfRightBlockElementIntoAncestorLeftBlockElement(
6547 aHTMLEditor
, MOZ_KnownLive(*mLeftBlockElement
),
6548 MOZ_KnownLive(*mRightBlockElement
),
6549 mPointContainingTheOtherBlockElement
,
6550 MOZ_KnownLive(*mInclusiveDescendantOfLeftBlockElement
),
6551 mNewListElementTagNameOfRightListElement
,
6552 MOZ_KnownLive(mPrecedingInvisibleBRElement
), aEditingHost
);
6553 if (MOZ_UNLIKELY(moveFirstLineResult
.isErr())) {
6555 "WhiteSpaceVisibilityKeeper::"
6556 "MergeFirstLineOfRightBlockElementIntoAncestorLeftBlockElement() "
6558 return moveFirstLineResult
.propagateErr();
6560 trackStartOfRightBlock
.FlushAndStopTracking();
6561 return ConvertMoveNodeResultToDeleteRangeResult(
6562 startOfRightContent
, moveFirstLineResult
.unwrap(), aEditingHost
);
6565 // Normal case. Blocks are siblings, or at least close enough. An example
6566 // of the latter is <p>paragraph</p><ul><li>one<li>two<li>three</ul>. The
6567 // first li and the p are not true siblings, but we still want to join them
6568 // if you backspace from li into p.
6569 MOZ_ASSERT(!mPointContainingTheOtherBlockElement
.IsSet());
6570 EditorDOMPoint startOfRightContent
=
6571 HTMLEditUtils::GetDeepestEditableStartPointOf
<EditorDOMPoint
>(
6572 *mRightBlockElement
);
6573 AutoTrackDOMPoint
trackStartOfRightBlock(aHTMLEditor
.RangeUpdaterRef(),
6574 &startOfRightContent
);
6575 Result
<MoveNodeResult
, nsresult
> moveFirstLineResult
=
6576 WhiteSpaceVisibilityKeeper::
6577 MergeFirstLineOfRightBlockElementIntoLeftBlockElement(
6578 aHTMLEditor
, MOZ_KnownLive(*mLeftBlockElement
),
6579 MOZ_KnownLive(*mRightBlockElement
),
6580 mNewListElementTagNameOfRightListElement
,
6581 MOZ_KnownLive(mPrecedingInvisibleBRElement
), aEditingHost
);
6582 if (MOZ_UNLIKELY(moveFirstLineResult
.isErr())) {
6584 "WhiteSpaceVisibilityKeeper::"
6585 "MergeFirstLineOfRightBlockElementIntoLeftBlockElement() failed");
6586 return moveFirstLineResult
.propagateErr();
6588 trackStartOfRightBlock
.FlushAndStopTracking();
6589 return ConvertMoveNodeResultToDeleteRangeResult(
6590 startOfRightContent
, moveFirstLineResult
.unwrap(), aEditingHost
);
6594 Result
<bool, nsresult
>
6595 HTMLEditor::AutoMoveOneLineHandler::CanMoveOrDeleteSomethingInLine(
6596 const EditorDOMPoint
& aPointInHardLine
, const Element
& aEditingHost
) {
6597 if (NS_WARN_IF(!aPointInHardLine
.IsSet()) ||
6598 NS_WARN_IF(aPointInHardLine
.IsInNativeAnonymousSubtree())) {
6599 return Err(NS_ERROR_INVALID_ARG
);
6602 RefPtr
<nsRange
> oneLineRange
= AutoClonedRangeArray::
6603 CreateRangeWrappingStartAndEndLinesContainingBoundaries(
6604 aPointInHardLine
, aPointInHardLine
,
6605 EditSubAction::eMergeBlockContents
,
6606 BlockInlineCheck::UseComputedDisplayOutsideStyle
, aEditingHost
);
6607 if (!oneLineRange
|| oneLineRange
->Collapsed() ||
6608 !oneLineRange
->IsPositioned() ||
6609 !oneLineRange
->GetStartContainer()->IsContent() ||
6610 !oneLineRange
->GetEndContainer()->IsContent()) {
6614 // If there is only a padding `<br>` element in a empty block, it's selected
6615 // by `UpdatePointsToSelectAllChildrenIfCollapsedInEmptyBlockElement()`.
6616 // However, it won't be moved. Although it'll be deleted,
6617 // AutoMoveOneLineHandler returns "ignored". Therefore, we should return
6618 // `false` in this case.
6619 if (nsIContent
* childContent
= oneLineRange
->GetChildAtStartOffset()) {
6620 if (childContent
->IsHTMLElement(nsGkAtoms::br
) &&
6621 childContent
->GetParent()) {
6622 if (const Element
* blockElement
=
6623 HTMLEditUtils::GetInclusiveAncestorElement(
6624 *childContent
->GetParent(),
6625 HTMLEditUtils::ClosestBlockElement
,
6626 BlockInlineCheck::UseComputedDisplayOutsideStyle
)) {
6627 if (HTMLEditUtils::IsEmptyNode(
6629 {EmptyCheckOption::TreatNonEditableContentAsInvisible
})) {
6636 nsINode
* commonAncestor
= oneLineRange
->GetClosestCommonInclusiveAncestor();
6637 // Currently, we move non-editable content nodes too.
6638 EditorRawDOMPoint
startPoint(oneLineRange
->StartRef());
6639 if (!startPoint
.IsEndOfContainer()) {
6642 EditorRawDOMPoint
endPoint(oneLineRange
->EndRef());
6643 if (!endPoint
.IsStartOfContainer()) {
6646 if (startPoint
.GetContainer() != commonAncestor
) {
6648 EditorRawDOMPoint
pointInParent(startPoint
.GetContainerAs
<nsIContent
>());
6649 if (NS_WARN_IF(!pointInParent
.IsInContentNode())) {
6650 return Err(NS_ERROR_FAILURE
);
6652 if (pointInParent
.GetContainer() == commonAncestor
) {
6653 startPoint
= pointInParent
;
6656 if (!pointInParent
.IsEndOfContainer()) {
6661 if (endPoint
.GetContainer() != commonAncestor
) {
6663 EditorRawDOMPoint
pointInParent(endPoint
.GetContainerAs
<nsIContent
>());
6664 if (NS_WARN_IF(!pointInParent
.IsInContentNode())) {
6665 return Err(NS_ERROR_FAILURE
);
6667 if (pointInParent
.GetContainer() == commonAncestor
) {
6668 endPoint
= pointInParent
;
6671 if (!pointInParent
.IsStartOfContainer()) {
6676 // If start point and end point in the common ancestor are direct siblings,
6677 // there is no content to move or delete.
6678 // E.g., `<b>abc<br>[</b><i>]<br>def</i>`.
6679 return startPoint
.GetNextSiblingOfChild() != endPoint
.GetChild();
6682 nsresult
HTMLEditor::AutoMoveOneLineHandler::Prepare(
6683 HTMLEditor
& aHTMLEditor
, const EditorDOMPoint
& aPointInHardLine
,
6684 const Element
& aEditingHost
) {
6685 MOZ_ASSERT(aHTMLEditor
.IsEditActionDataAvailable());
6686 MOZ_ASSERT(aPointInHardLine
.IsInContentNode());
6687 MOZ_ASSERT(mPointToInsert
.IsSetAndValid());
6689 MOZ_LOG(gOneLineMoverLog
, LogLevel::Info
,
6690 ("Prepare(aHTMLEditor=%p, aPointInHardLine=%s, aEditingHost=%s), "
6691 "mPointToInsert=%s, mMoveToEndOfContainer=%s",
6692 &aHTMLEditor
, ToString(aPointInHardLine
).c_str(),
6693 ToString(aEditingHost
).c_str(), ToString(mPointToInsert
).c_str(),
6694 ForceMoveToEndOfContainer() ? "MoveToEndOfContainer::Yes"
6695 : "MoveToEndOfContainer::No"));
6697 if (NS_WARN_IF(mPointToInsert
.IsInNativeAnonymousSubtree())) {
6699 gOneLineMoverLog
, LogLevel::Error
,
6700 ("Failed because mPointToInsert was in a native anonymous subtree"));
6701 return Err(NS_ERROR_INVALID_ARG
);
6704 mSrcInclusiveAncestorBlock
=
6705 aPointInHardLine
.IsInContentNode()
6706 ? HTMLEditUtils::GetInclusiveAncestorElement(
6707 *aPointInHardLine
.ContainerAs
<nsIContent
>(),
6708 HTMLEditUtils::ClosestBlockElement
,
6709 BlockInlineCheck::UseComputedDisplayOutsideStyle
)
6711 mDestInclusiveAncestorBlock
=
6712 mPointToInsert
.IsInContentNode()
6713 ? HTMLEditUtils::GetInclusiveAncestorElement(
6714 *mPointToInsert
.ContainerAs
<nsIContent
>(),
6715 HTMLEditUtils::ClosestBlockElement
,
6716 BlockInlineCheck::UseComputedDisplayOutsideStyle
)
6718 mMovingToParentBlock
=
6719 mDestInclusiveAncestorBlock
&& mSrcInclusiveAncestorBlock
&&
6720 mDestInclusiveAncestorBlock
!= mSrcInclusiveAncestorBlock
&&
6721 mSrcInclusiveAncestorBlock
->IsInclusiveDescendantOf(
6722 mDestInclusiveAncestorBlock
);
6723 mTopmostSrcAncestorBlockInDestBlock
=
6724 mMovingToParentBlock
6725 ? AutoMoveOneLineHandler::
6726 GetMostDistantInclusiveAncestorBlockInSpecificAncestorElement(
6727 *mSrcInclusiveAncestorBlock
, *mDestInclusiveAncestorBlock
)
6729 MOZ_ASSERT_IF(mMovingToParentBlock
, mTopmostSrcAncestorBlockInDestBlock
);
6731 mPreserveWhiteSpaceStyle
=
6732 AutoMoveOneLineHandler::ConsiderWhetherPreserveWhiteSpaceStyle(
6733 aPointInHardLine
.GetContainerAs
<nsIContent
>(),
6734 mDestInclusiveAncestorBlock
);
6736 AutoClonedRangeArray
rangesToWrapTheLine(aPointInHardLine
);
6737 rangesToWrapTheLine
.ExtendRangesToWrapLines(
6738 EditSubAction::eMergeBlockContents
,
6739 BlockInlineCheck::UseComputedDisplayOutsideStyle
,
6740 mTopmostSrcAncestorBlockInDestBlock
? *mTopmostSrcAncestorBlockInDestBlock
6742 MOZ_ASSERT(rangesToWrapTheLine
.Ranges().Length() <= 1u);
6743 mLineRange
= EditorDOMRange(rangesToWrapTheLine
.FirstRangeRef());
6745 MOZ_LOG(gOneLineMoverLog
, LogLevel::Info
,
6746 ("mSrcInclusiveAncestorBlock=%s, mDestInclusiveAncestorBlock=%s, "
6747 "mMovingToParentBlock=%s, mTopmostSrcAncestorBlockInDestBlock=%s, "
6748 "mPreserveWhiteSpaceStyle=%s, mLineRange=%s",
6749 mSrcInclusiveAncestorBlock
6750 ? ToString(*mSrcInclusiveAncestorBlock
).c_str()
6752 mDestInclusiveAncestorBlock
6753 ? ToString(*mDestInclusiveAncestorBlock
).c_str()
6755 mMovingToParentBlock
? "true" : "false",
6756 mTopmostSrcAncestorBlockInDestBlock
6757 ? ToString(*mTopmostSrcAncestorBlockInDestBlock
).c_str()
6759 ToString(mPreserveWhiteSpaceStyle
).c_str(),
6760 ToString(mLineRange
).c_str()));
6765 Result
<CaretPoint
, nsresult
>
6766 HTMLEditor::AutoMoveOneLineHandler::SplitToMakeTheLineIsolated(
6767 HTMLEditor
& aHTMLEditor
, const nsIContent
& aNewContainer
,
6768 const Element
& aEditingHost
,
6769 nsTArray
<OwningNonNull
<nsIContent
>>& aOutArrayOfContents
) const {
6770 AutoClonedRangeArray
rangesToWrapTheLine(mLineRange
);
6771 Result
<EditorDOMPoint
, nsresult
> splitResult
=
6773 .SplitTextAtEndBoundariesAndInlineAncestorsAtBothBoundaries(
6774 aHTMLEditor
, BlockInlineCheck::UseComputedDisplayOutsideStyle
,
6775 aEditingHost
, &aNewContainer
);
6776 if (MOZ_UNLIKELY(splitResult
.isErr())) {
6778 "AutoClonedRangeArray::"
6779 "SplitTextAtEndBoundariesAndInlineAncestorsAtBothBoundaries() failed");
6780 return Err(splitResult
.unwrapErr());
6782 EditorDOMPoint pointToPutCaret
;
6783 if (splitResult
.inspect().IsSet()) {
6784 pointToPutCaret
= splitResult
.unwrap();
6786 nsresult rv
= rangesToWrapTheLine
.CollectEditTargetNodes(
6787 aHTMLEditor
, aOutArrayOfContents
, EditSubAction::eMergeBlockContents
,
6788 AutoClonedRangeArray::CollectNonEditableNodes::Yes
);
6789 if (NS_FAILED(rv
)) {
6791 "AutoClonedRangeArray::CollectEditTargetNodes(EditSubAction::"
6792 "eMergeBlockContents, CollectNonEditableNodes::Yes) failed");
6795 return CaretPoint(pointToPutCaret
);
6799 Element
* HTMLEditor::AutoMoveOneLineHandler::
6800 GetMostDistantInclusiveAncestorBlockInSpecificAncestorElement(
6801 Element
& aBlockElement
, const Element
& aAncestorElement
) {
6802 MOZ_ASSERT(aBlockElement
.IsInclusiveDescendantOf(&aAncestorElement
));
6803 MOZ_ASSERT(HTMLEditUtils::IsBlockElement(
6804 aBlockElement
, BlockInlineCheck::UseComputedDisplayOutsideStyle
));
6806 if (&aBlockElement
== &aAncestorElement
) {
6810 Element
* lastBlockAncestor
= &aBlockElement
;
6811 for (Element
* element
: aBlockElement
.InclusiveAncestorsOfType
<Element
>()) {
6812 if (element
== &aAncestorElement
) {
6813 return lastBlockAncestor
;
6815 if (HTMLEditUtils::IsBlockElement(
6817 BlockInlineCheck::UseComputedDisplayOutsideStyle
)) {
6818 lastBlockAncestor
= element
;
6825 HTMLEditor::PreserveWhiteSpaceStyle
6826 HTMLEditor::AutoMoveOneLineHandler::ConsiderWhetherPreserveWhiteSpaceStyle(
6827 const nsIContent
* aContentInLine
,
6828 const Element
* aInclusiveAncestorBlockOfInsertionPoint
) {
6829 if (MOZ_UNLIKELY(!aInclusiveAncestorBlockOfInsertionPoint
)) {
6830 return PreserveWhiteSpaceStyle::No
;
6833 // If we move content from or to <pre>, we don't need to preserve the
6834 // white-space style for compatibility with both our traditional behavior
6835 // and the other browsers.
6837 // TODO: If `white-space` is specified by non-UA stylesheet, we should
6838 // preserve it even if the right block is <pre> for compatibility with the
6840 const auto IsInclusiveDescendantOfPre
= [](const nsIContent
& aContent
) {
6841 // If the content has different `white-space` style from <pre>, we
6842 // shouldn't treat it as a descendant of <pre> because web apps or
6843 // the user intent to treat the white-spaces in aContent not as `pre`.
6844 if (EditorUtils::GetComputedWhiteSpaceStyles(aContent
).valueOr(std::pair(
6845 StyleWhiteSpaceCollapse::Collapse
, StyleTextWrapMode::Wrap
)) !=
6846 std::pair(StyleWhiteSpaceCollapse::Preserve
,
6847 StyleTextWrapMode::Nowrap
)) {
6850 for (const Element
* element
:
6851 aContent
.InclusiveAncestorsOfType
<Element
>()) {
6852 if (element
->IsHTMLElement(nsGkAtoms::pre
)) {
6858 if (IsInclusiveDescendantOfPre(*aInclusiveAncestorBlockOfInsertionPoint
) ||
6859 MOZ_UNLIKELY(!aContentInLine
) ||
6860 IsInclusiveDescendantOfPre(*aContentInLine
)) {
6861 return PreserveWhiteSpaceStyle::No
;
6863 return PreserveWhiteSpaceStyle::Yes
;
6866 Result
<MoveNodeResult
, nsresult
> HTMLEditor::AutoMoveOneLineHandler::Run(
6867 HTMLEditor
& aHTMLEditor
, const Element
& aEditingHost
) {
6868 EditorDOMPoint
pointToInsert(NextInsertionPointRef());
6869 MOZ_ASSERT(pointToInsert
.IsInContentNode());
6872 gOneLineMoverLog
, LogLevel::Info
,
6873 ("Run(aHTMLEditor=%p, aEditingHost=%s), pointToInsert=%s", &aHTMLEditor
,
6874 ToString(aEditingHost
).c_str(), ToString(pointToInsert
).c_str()));
6876 EditorDOMPoint pointToPutCaret
;
6877 AutoTArray
<OwningNonNull
<nsIContent
>, 64> arrayOfContents
;
6879 AutoTrackDOMPoint
tackPointToInsert(aHTMLEditor
.RangeUpdaterRef(),
6882 Result
<CaretPoint
, nsresult
> splitAtLineEdgesResult
=
6883 SplitToMakeTheLineIsolated(
6885 MOZ_KnownLive(*pointToInsert
.ContainerAs
<nsIContent
>()),
6886 aEditingHost
, arrayOfContents
);
6887 if (MOZ_UNLIKELY(splitAtLineEdgesResult
.isErr())) {
6888 NS_WARNING("AutoMoveOneLineHandler::SplitToMakeTheLineIsolated() failed");
6889 MOZ_LOG(gOneLineMoverLog
, LogLevel::Error
,
6890 ("Run: SplitToMakeTheLineIsolated() failed"));
6891 return splitAtLineEdgesResult
.propagateErr();
6893 splitAtLineEdgesResult
.unwrap().MoveCaretPointTo(
6894 pointToPutCaret
, {SuggestCaret::OnlyIfHasSuggestion
});
6895 MOZ_LOG(gOneLineMoverLog
, LogLevel::Verbose
,
6896 ("Run: pointToPutCaret=%s", ToString(pointToPutCaret
).c_str()));
6898 Result
<EditorDOMPoint
, nsresult
> splitAtBRElementsResult
=
6899 aHTMLEditor
.MaybeSplitElementsAtEveryBRElement(
6900 arrayOfContents
, EditSubAction::eMergeBlockContents
);
6901 if (MOZ_UNLIKELY(splitAtBRElementsResult
.isErr())) {
6903 "HTMLEditor::MaybeSplitElementsAtEveryBRElement(EditSubAction::"
6904 "eMergeBlockContents) failed");
6905 MOZ_LOG(gOneLineMoverLog
, LogLevel::Error
,
6906 ("Run: MaybeSplitElementsAtEveryBRElement() failed"));
6907 return splitAtBRElementsResult
.propagateErr();
6909 if (splitAtBRElementsResult
.inspect().IsSet()) {
6910 pointToPutCaret
= splitAtBRElementsResult
.unwrap();
6912 MOZ_LOG(gOneLineMoverLog
, LogLevel::Verbose
,
6913 ("Run: pointToPutCaret=%s", ToString(pointToPutCaret
).c_str()));
6916 if (!pointToInsert
.IsSetAndValid()) {
6917 MOZ_LOG(gOneLineMoverLog
, LogLevel::Error
,
6918 ("Run: Failed because pointToInsert pointed invalid position"));
6919 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
);
6922 if (aHTMLEditor
.AllowsTransactionsToChangeSelection() &&
6923 pointToPutCaret
.IsSet()) {
6924 nsresult rv
= aHTMLEditor
.CollapseSelectionTo(pointToPutCaret
);
6925 if (NS_FAILED(rv
)) {
6926 NS_WARNING("EditorBase::CollapseSelectionTo() failed");
6927 MOZ_LOG(gOneLineMoverLog
, LogLevel::Error
,
6928 ("Run: Failed because of "
6929 "aHTMLEditor.CollapseSelectionTo(pointToPutCaret) failure"));
6934 if (arrayOfContents
.IsEmpty()) {
6935 MOZ_LOG(gOneLineMoverLog
, LogLevel::Info
,
6936 ("Run: Did nothing because of no content to be moved"));
6937 return MoveNodeResult::IgnoredResult(std::move(pointToInsert
));
6940 // Track the range which contains the moved contents.
6941 if (ForceMoveToEndOfContainer()) {
6942 pointToInsert
= NextInsertionPointRef();
6944 EditorDOMRange
movedContentRange(pointToInsert
);
6945 MoveNodeResult moveContentsInLineResult
=
6946 MoveNodeResult::IgnoredResult(pointToInsert
);
6947 for (const OwningNonNull
<nsIContent
>& content
: arrayOfContents
) {
6948 MOZ_LOG(gOneLineMoverLog
, LogLevel::Info
,
6949 ("Run: content=%s, pointToInsert=%s, movedContentRange=%s, "
6950 "mPointToInsert=%s",
6951 ToString(content
.ref()).c_str(), ToString(pointToInsert
).c_str(),
6952 ToString(movedContentRange
).c_str(),
6953 ToString(mPointToInsert
).c_str()));
6955 AutoEditorDOMRangeChildrenInvalidator
lockOffsets(movedContentRange
);
6956 AutoTrackDOMRange
trackMovedContentRange(aHTMLEditor
.RangeUpdaterRef(),
6957 &movedContentRange
);
6958 // If the content is a block element, move all children of it to the
6959 // new container, and then, remove the (probably) empty block element.
6960 if (HTMLEditUtils::IsBlockElement(
6961 content
, BlockInlineCheck::UseComputedDisplayOutsideStyle
)) {
6962 MOZ_LOG(gOneLineMoverLog
, LogLevel::Info
,
6963 ("Run: Unwrapping children of content because of a block"));
6964 AutoTrackDOMMoveNodeResult
trackMoveContentsInLineResult(
6965 aHTMLEditor
.RangeUpdaterRef(), &moveContentsInLineResult
);
6966 Result
<MoveNodeResult
, nsresult
> moveChildrenResult
=
6967 aHTMLEditor
.MoveChildrenWithTransaction(
6968 MOZ_KnownLive(*content
->AsElement()), pointToInsert
,
6969 mPreserveWhiteSpaceStyle
, RemoveIfCommentNode::Yes
);
6970 if (MOZ_UNLIKELY(moveChildrenResult
.isErr())) {
6971 NS_WARNING("HTMLEditor::MoveChildrenWithTransaction() failed");
6972 MOZ_LOG(gOneLineMoverLog
, LogLevel::Error
,
6973 ("Run: MoveChildrenWithTransaction() failed"));
6974 moveContentsInLineResult
.IgnoreCaretPointSuggestion();
6975 return moveChildrenResult
;
6977 trackMoveContentsInLineResult
.FlushAndStopTracking();
6978 moveContentsInLineResult
|= moveChildrenResult
.inspect();
6980 AutoTrackDOMMoveNodeResult
trackMoveContentsInLineResult(
6981 aHTMLEditor
.RangeUpdaterRef(), &moveContentsInLineResult
);
6982 // MOZ_KnownLive due to bug 1620312
6984 aHTMLEditor
.DeleteNodeWithTransaction(MOZ_KnownLive(content
));
6985 if (NS_WARN_IF(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
6986 MOZ_LOG(gOneLineMoverLog
, LogLevel::Error
,
6987 ("Run: Aborted because DeleteNodeWithTransaction() caused "
6988 "destroying the editor"));
6989 moveContentsInLineResult
.IgnoreCaretPointSuggestion();
6990 return Err(NS_ERROR_EDITOR_DESTROYED
);
6992 if (NS_FAILED(rv
)) {
6994 "EditorBase::DeleteNodeWithTransaction() failed, but ignored");
6996 gOneLineMoverLog
, LogLevel::Warning
,
6997 ("Run: Failed to delete content but the error was ignored"));
7001 // If the moving content is a comment node or an empty inline node, we
7002 // don't want it to appear in the dist paragraph.
7003 else if (content
->IsComment() ||
7004 (content
->IsText() && !content
->AsText()->TextDataLength()) ||
7005 HTMLEditUtils::IsEmptyInlineContainer(
7007 {EmptyCheckOption::TreatSingleBRElementAsVisible
,
7008 EmptyCheckOption::TreatListItemAsVisible
,
7009 EmptyCheckOption::TreatTableCellAsVisible
,
7010 EmptyCheckOption::TreatNonEditableContentAsInvisible
},
7011 BlockInlineCheck::UseComputedDisplayOutsideStyle
)) {
7012 nsCOMPtr
<nsIContent
> emptyContent
=
7013 HTMLEditUtils::GetMostDistantAncestorEditableEmptyInlineElement(
7014 content
, BlockInlineCheck::UseComputedDisplayOutsideStyle
,
7015 &aEditingHost
, pointToInsert
.ContainerAs
<nsIContent
>());
7016 if (!emptyContent
) {
7017 emptyContent
= content
;
7019 MOZ_LOG(gOneLineMoverLog
, LogLevel::Info
,
7020 ("Run: Deleting content because of %s%s",
7021 content
->IsComment() ? "a comment node"
7022 : content
->IsText() ? "an empty text node"
7023 : "an empty inline container",
7024 content
!= emptyContent
7025 ? nsPrintfCString(" (deleting topmost empty ancestor: %s)",
7026 ToString(*emptyContent
).c_str())
7029 AutoTrackDOMMoveNodeResult
trackMoveContentsInLineResult(
7030 aHTMLEditor
.RangeUpdaterRef(), &moveContentsInLineResult
);
7031 nsresult rv
= aHTMLEditor
.DeleteNodeWithTransaction(*emptyContent
);
7032 if (NS_FAILED(rv
)) {
7033 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
7034 MOZ_LOG(gOneLineMoverLog
, LogLevel::Error
,
7035 ("Run: DeleteNodeWithTransaction() failed"));
7036 moveContentsInLineResult
.IgnoreCaretPointSuggestion();
7040 MOZ_LOG(gOneLineMoverLog
, LogLevel::Info
, ("Run: Moving content"));
7041 AutoTrackDOMMoveNodeResult
trackMoveContentsInLineResult(
7042 aHTMLEditor
.RangeUpdaterRef(), &moveContentsInLineResult
);
7043 // MOZ_KnownLive due to bug 1620312
7044 Result
<MoveNodeResult
, nsresult
> moveNodeOrChildrenResult
=
7045 aHTMLEditor
.MoveNodeOrChildrenWithTransaction(
7046 MOZ_KnownLive(content
), pointToInsert
, mPreserveWhiteSpaceStyle
,
7047 RemoveIfCommentNode::Yes
);
7048 if (MOZ_UNLIKELY(moveNodeOrChildrenResult
.isErr())) {
7049 NS_WARNING("HTMLEditor::MoveNodeOrChildrenWithTransaction() failed");
7050 MOZ_LOG(gOneLineMoverLog
, LogLevel::Error
,
7051 ("Run: MoveNodeOrChildrenWithTransaction() failed"));
7052 moveContentsInLineResult
.IgnoreCaretPointSuggestion();
7053 return moveNodeOrChildrenResult
;
7055 trackMoveContentsInLineResult
.FlushAndStopTracking();
7056 moveContentsInLineResult
|= moveNodeOrChildrenResult
.inspect();
7059 MOZ_LOG(gOneLineMoverLog
, LogLevel::Info
,
7060 ("Run: movedContentRange=%s, mPointToInsert=%s",
7061 ToString(movedContentRange
).c_str(),
7062 ToString(mPointToInsert
).c_str()));
7063 moveContentsInLineResult
.ForceToMarkAsHandled();
7064 if (NS_WARN_IF(!movedContentRange
.IsPositioned())) {
7065 MOZ_LOG(gOneLineMoverLog
, LogLevel::Error
,
7066 ("Run: Failed because movedContentRange was not positioned"));
7067 moveContentsInLineResult
.IgnoreCaretPointSuggestion();
7068 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
);
7070 // For backward compatibility, we should move contents to end of the
7071 // container if the instance is created without specific insertion point.
7072 if (ForceMoveToEndOfContainer()) {
7073 pointToInsert
= NextInsertionPointRef();
7074 MOZ_ASSERT(pointToInsert
.IsSet());
7075 MOZ_ASSERT(movedContentRange
.StartRef().EqualsOrIsBefore(pointToInsert
));
7076 movedContentRange
.SetEnd(pointToInsert
);
7077 MOZ_LOG(gOneLineMoverLog
, LogLevel::Debug
,
7078 ("Run: Updated movedContentRange end to next insertion point"));
7080 // And also if pointToInsert has been made invalid with removing preceding
7081 // children, we should move the content to the end of the container.
7082 else if (aHTMLEditor
.MayHaveMutationEventListeners() &&
7083 MOZ_UNLIKELY(!moveContentsInLineResult
.NextInsertionPointRef()
7084 .IsSetAndValid())) {
7085 mPointToInsert
.SetToEndOf(mPointToInsert
.GetContainer());
7086 pointToInsert
= NextInsertionPointRef();
7087 movedContentRange
.SetEnd(pointToInsert
);
7088 MOZ_LOG(gOneLineMoverLog
, LogLevel::Debug
,
7089 ("Run: Updated mPointToInsert to end of container and updated "
7090 "movedContentRange"));
7092 MOZ_DIAGNOSTIC_ASSERT(
7093 moveContentsInLineResult
.NextInsertionPointRef().IsSet());
7094 mPointToInsert
= moveContentsInLineResult
.NextInsertionPointRef();
7095 pointToInsert
= NextInsertionPointRef();
7096 if (!aHTMLEditor
.MayHaveMutationEventListeners() ||
7097 movedContentRange
.EndRef().IsBefore(pointToInsert
)) {
7098 MOZ_ASSERT(pointToInsert
.IsSet());
7100 movedContentRange
.StartRef().EqualsOrIsBefore(pointToInsert
));
7101 movedContentRange
.SetEnd(pointToInsert
);
7102 MOZ_LOG(gOneLineMoverLog
, LogLevel::Debug
,
7103 ("Run: Updated mPointToInsert and updated movedContentRange"));
7105 MOZ_LOG(gOneLineMoverLog
, LogLevel::Debug
,
7106 ("Run: Updated only mPointToInsert"));
7111 // Nothing has been moved, we don't need to clean up unnecessary <br> element.
7112 // And also if we're not moving content into a block, we can quit right now.
7113 if (moveContentsInLineResult
.Ignored() ||
7114 MOZ_UNLIKELY(!mDestInclusiveAncestorBlock
)) {
7115 MOZ_LOG(gOneLineMoverLog
, LogLevel::Info
,
7116 (moveContentsInLineResult
.Ignored()
7117 ? "Run: Did nothing for any children"
7118 : "Run: Finished (not dest block)"));
7119 return std::move(moveContentsInLineResult
);
7122 // If we couldn't track the range to clean up, we should just stop cleaning up
7123 // because returning error from here may change the behavior of web apps using
7124 // mutation event listeners.
7125 if (MOZ_UNLIKELY(!movedContentRange
.IsPositioned() ||
7126 movedContentRange
.Collapsed())) {
7127 MOZ_LOG(gOneLineMoverLog
, LogLevel::Info
,
7128 (!movedContentRange
.IsPositioned()
7129 ? "Run: Finished (Couldn't track moved line)"
7130 : "Run: Finished (Moved line was empty)"));
7131 return std::move(moveContentsInLineResult
);
7135 AutoTrackDOMMoveNodeResult
trackMoveContentsInLineResult(
7136 aHTMLEditor
.RangeUpdaterRef(), &moveContentsInLineResult
);
7137 nsresult rv
= DeleteUnnecessaryTrailingLineBreakInMovedLineEnd(
7138 aHTMLEditor
, movedContentRange
, aEditingHost
);
7139 if (NS_FAILED(rv
)) {
7141 "AutoMoveOneLineHandler::"
7142 "DeleteUnnecessaryTrailingLineBreakInMovedLineEnd() failed");
7144 gOneLineMoverLog
, LogLevel::Error
,
7145 ("Run: DeleteUnnecessaryTrailingLineBreakInMovedLineEnd() failed"));
7146 moveContentsInLineResult
.IgnoreCaretPointSuggestion();
7151 MOZ_LOG(gOneLineMoverLog
, LogLevel::Info
, ("Run: Finished"));
7152 return std::move(moveContentsInLineResult
);
7155 nsresult
HTMLEditor::AutoMoveOneLineHandler::
7156 DeleteUnnecessaryTrailingLineBreakInMovedLineEnd(
7157 HTMLEditor
& aHTMLEditor
, const EditorDOMRange
& aMovedContentRange
,
7158 const Element
& aEditingHost
) const {
7159 MOZ_ASSERT(mDestInclusiveAncestorBlock
);
7160 MOZ_ASSERT(aMovedContentRange
.IsPositioned());
7161 MOZ_ASSERT(!aMovedContentRange
.Collapsed());
7163 // If we didn't preserve white-space for backward compatibility and
7164 // white-space becomes not preformatted, we need to clean it up the last text
7165 // node if it ends with a preformatted line break.
7166 if (mPreserveWhiteSpaceStyle
== PreserveWhiteSpaceStyle::No
) {
7167 const RefPtr
<Text
> textNodeEndingWithUnnecessaryLineBreak
= [&]() -> Text
* {
7168 Text
* lastTextNode
= Text::FromNodeOrNull(
7169 mMovingToParentBlock
7170 ? HTMLEditUtils::GetPreviousContent(
7171 *mTopmostSrcAncestorBlockInDestBlock
,
7172 {WalkTreeOption::StopAtBlockBoundary
},
7173 BlockInlineCheck::UseComputedDisplayOutsideStyle
,
7174 mDestInclusiveAncestorBlock
)
7175 : HTMLEditUtils::GetLastLeafContent(
7176 *mDestInclusiveAncestorBlock
,
7177 {LeafNodeType::LeafNodeOrNonEditableNode
}));
7178 if (!lastTextNode
||
7179 !HTMLEditUtils::IsSimplyEditableNode(*lastTextNode
)) {
7182 const nsTextFragment
& textFragment
= lastTextNode
->TextFragment();
7183 const char16_t lastCh
=
7184 textFragment
.GetLength()
7185 ? textFragment
.CharAt(textFragment
.GetLength() - 1u)
7187 return lastCh
== HTMLEditUtils::kNewLine
&&
7188 !EditorUtils::IsNewLinePreformatted(*lastTextNode
)
7192 if (textNodeEndingWithUnnecessaryLineBreak
) {
7193 if (textNodeEndingWithUnnecessaryLineBreak
->TextDataLength() == 1u) {
7194 const RefPtr
<Element
> inlineElement
=
7195 HTMLEditUtils::GetMostDistantAncestorEditableEmptyInlineElement(
7196 *textNodeEndingWithUnnecessaryLineBreak
,
7197 BlockInlineCheck::UseComputedDisplayOutsideStyle
,
7199 nsresult rv
= aHTMLEditor
.DeleteNodeWithTransaction(
7200 inlineElement
? static_cast<nsIContent
&>(*inlineElement
)
7201 : static_cast<nsIContent
&>(
7202 *textNodeEndingWithUnnecessaryLineBreak
));
7203 if (NS_FAILED(rv
)) {
7204 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
7208 Result
<CaretPoint
, nsresult
> caretPointOrError
=
7209 aHTMLEditor
.DeleteTextWithTransaction(
7210 *textNodeEndingWithUnnecessaryLineBreak
,
7211 textNodeEndingWithUnnecessaryLineBreak
->TextDataLength() - 1u,
7213 if (MOZ_UNLIKELY(caretPointOrError
.isErr())) {
7214 NS_WARNING("HTMLEditor::DeleteTextWithTransaction() failed");
7215 return caretPointOrError
.propagateErr();
7217 nsresult rv
= caretPointOrError
.inspect().SuggestCaretPointTo(
7218 aHTMLEditor
, {SuggestCaret::OnlyIfHasSuggestion
,
7219 SuggestCaret::OnlyIfTransactionsAllowedToDoIt
,
7220 SuggestCaret::AndIgnoreTrivialError
});
7221 if (NS_FAILED(rv
)) {
7222 NS_WARNING("CaretPoint::SuggestCaretPointTo() failed");
7225 NS_WARNING_ASSERTION(
7226 rv
!= NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR
,
7227 "CaretPoint::SuggestCaretPointTo() failed, but ignored");
7232 const Maybe
<EditorLineBreak
> lastLineBreak
=
7233 mMovingToParentBlock
7234 ? HTMLEditUtils::GetUnnecessaryLineBreak
<EditorLineBreak
>(
7235 *mTopmostSrcAncestorBlockInDestBlock
,
7236 ScanLineBreak::BeforeBlock
)
7237 : HTMLEditUtils::GetUnnecessaryLineBreak
<EditorLineBreak
>(
7238 *mDestInclusiveAncestorBlock
, ScanLineBreak::AtEndOfBlock
);
7239 if (lastLineBreak
.isNothing()) {
7242 const auto atUnnecessaryLineBreak
= lastLineBreak
->To
<EditorRawDOMPoint
>();
7243 if (NS_WARN_IF(!atUnnecessaryLineBreak
.IsSet())) {
7244 return NS_ERROR_FAILURE
;
7246 // If the found unnecessary line break is not what we moved above, we
7247 // shouldn't remove it. E.g., the web app may have inserted it intentionally.
7248 MOZ_ASSERT(aMovedContentRange
.StartRef().IsSetAndValid());
7249 MOZ_ASSERT(aMovedContentRange
.EndRef().IsSetAndValid());
7250 if (!aMovedContentRange
.Contains(atUnnecessaryLineBreak
)) {
7254 AutoTransactionsConserveSelection
dontChangeMySelection(aHTMLEditor
);
7255 Result
<EditorDOMPoint
, nsresult
> lineBreakPointOrError
=
7256 aHTMLEditor
.DeleteLineBreakWithTransaction(
7257 lastLineBreak
.ref(),
7258 aEditingHost
.IsContentEditablePlainTextOnly() ? nsIEditor::eNoStrip
7259 : nsIEditor::eStrip
,
7261 if (MOZ_UNLIKELY(lineBreakPointOrError
.isErr())) {
7262 NS_WARNING("HTMLEditor::DeleteLineBreakWithTransaction() failed");
7263 return lineBreakPointOrError
.propagateErr();
7268 Result
<bool, nsresult
> HTMLEditor::CanMoveNodeOrChildren(
7269 const nsIContent
& aContent
, const nsINode
& aNewContainer
) const {
7270 if (HTMLEditUtils::CanNodeContain(aNewContainer
, aContent
)) {
7273 if (aContent
.IsElement()) {
7274 return CanMoveChildren(*aContent
.AsElement(), aNewContainer
);
7279 Result
<MoveNodeResult
, nsresult
> HTMLEditor::MoveNodeOrChildrenWithTransaction(
7280 nsIContent
& aContentToMove
, const EditorDOMPoint
& aPointToInsert
,
7281 PreserveWhiteSpaceStyle aPreserveWhiteSpaceStyle
,
7282 RemoveIfCommentNode aRemoveIfCommentNode
) {
7283 MOZ_ASSERT(IsEditActionDataAvailable());
7284 MOZ_ASSERT(aPointToInsert
.IsInContentNode());
7286 const auto destWhiteSpaceStyles
=
7287 [&]() -> Maybe
<std::pair
<StyleWhiteSpaceCollapse
, StyleTextWrapMode
>> {
7288 if (aPreserveWhiteSpaceStyle
== PreserveWhiteSpaceStyle::No
||
7289 !aPointToInsert
.IsInContentNode()) {
7292 auto styles
= EditorUtils::GetComputedWhiteSpaceStyles(
7293 *aPointToInsert
.ContainerAs
<nsIContent
>());
7294 if (NS_WARN_IF(styles
.isSome() &&
7295 styles
.value().first
==
7296 StyleWhiteSpaceCollapse::PreserveSpaces
)) {
7301 const auto srcWhiteSpaceStyles
=
7302 [&]() -> Maybe
<std::pair
<StyleWhiteSpaceCollapse
, StyleTextWrapMode
>> {
7303 if (aPreserveWhiteSpaceStyle
== PreserveWhiteSpaceStyle::No
) {
7306 auto styles
= EditorUtils::GetComputedWhiteSpaceStyles(aContentToMove
);
7307 if (NS_WARN_IF(styles
.isSome() &&
7308 styles
.value().first
==
7309 StyleWhiteSpaceCollapse::PreserveSpaces
)) {
7314 // Get the `white-space` shorthand form for the given collapse + mode pair.
7315 const auto GetWhiteSpaceStyleValue
=
7316 [](std::pair
<StyleWhiteSpaceCollapse
, StyleTextWrapMode
> aStyles
) {
7317 if (aStyles
.second
== StyleTextWrapMode::Wrap
) {
7318 switch (aStyles
.first
) {
7319 case StyleWhiteSpaceCollapse::Collapse
:
7320 return u
"normal"_ns
;
7321 case StyleWhiteSpaceCollapse::Preserve
:
7322 return u
"pre-wrap"_ns
;
7323 case StyleWhiteSpaceCollapse::PreserveBreaks
:
7324 return u
"pre-line"_ns
;
7325 case StyleWhiteSpaceCollapse::PreserveSpaces
:
7326 return u
"preserve-spaces"_ns
;
7327 case StyleWhiteSpaceCollapse::BreakSpaces
:
7328 return u
"break-spaces"_ns
;
7331 switch (aStyles
.first
) {
7332 case StyleWhiteSpaceCollapse::Collapse
:
7333 return u
"nowrap"_ns
;
7334 case StyleWhiteSpaceCollapse::Preserve
:
7336 case StyleWhiteSpaceCollapse::PreserveBreaks
:
7337 return u
"nowrap preserve-breaks"_ns
;
7338 case StyleWhiteSpaceCollapse::PreserveSpaces
:
7339 return u
"nowrap preserve-spaces"_ns
;
7340 case StyleWhiteSpaceCollapse::BreakSpaces
:
7341 return u
"nowrap break-spaces"_ns
;
7344 MOZ_ASSERT_UNREACHABLE("all values should be handled above!");
7345 return u
"normal"_ns
;
7348 if (aRemoveIfCommentNode
== RemoveIfCommentNode::Yes
&&
7349 aContentToMove
.IsComment()) {
7350 EditorDOMPoint
pointToInsert(aPointToInsert
);
7352 AutoTrackDOMPoint
trackPointToInsert(RangeUpdaterRef(), &pointToInsert
);
7353 nsresult rv
= DeleteNodeWithTransaction(aContentToMove
);
7354 if (NS_FAILED(rv
)) {
7355 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
7359 if (NS_WARN_IF(!pointToInsert
.IsSetAndValid())) {
7360 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
);
7362 return MoveNodeResult::HandledResult(std::move(pointToInsert
));
7365 // Check if this node can go into the destination node
7366 if (HTMLEditUtils::CanNodeContain(*aPointToInsert
.GetContainer(),
7368 EditorDOMPoint
pointToInsert(aPointToInsert
);
7369 // Preserve white-space in the new position with using `style` attribute.
7370 // This is additional path from point of view of our traditional behavior.
7371 // Therefore, ignore errors especially if we got unexpected DOM tree.
7372 if (destWhiteSpaceStyles
.isSome() && srcWhiteSpaceStyles
.isSome() &&
7373 destWhiteSpaceStyles
.value() != srcWhiteSpaceStyles
.value()) {
7374 // Set `white-space` with `style` attribute if it's nsStyledElement.
7375 if (nsStyledElement
* styledElement
=
7376 nsStyledElement::FromNode(&aContentToMove
)) {
7377 DebugOnly
<nsresult
> rvIgnored
=
7378 CSSEditUtils::SetCSSPropertyWithTransaction(
7379 *this, MOZ_KnownLive(*styledElement
), *nsGkAtoms::white_space
,
7380 GetWhiteSpaceStyleValue(srcWhiteSpaceStyles
.value()));
7381 if (NS_WARN_IF(Destroyed())) {
7382 return Err(NS_ERROR_EDITOR_DESTROYED
);
7384 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored
),
7385 "CSSEditUtils::SetCSSPropertyWithTransaction("
7386 "nsGkAtoms::white_space) failed, but ignored");
7388 // Otherwise, if the dest container can have <span> element and <span>
7389 // element can have the moving content node, we should insert it.
7390 else if (HTMLEditUtils::CanNodeContain(*aPointToInsert
.GetContainer(),
7391 *nsGkAtoms::span
) &&
7392 HTMLEditUtils::CanNodeContain(*nsGkAtoms::span
,
7394 RefPtr
<Element
> newSpanElement
= CreateHTMLContent(nsGkAtoms::span
);
7395 if (NS_WARN_IF(!newSpanElement
)) {
7396 return Err(NS_ERROR_FAILURE
);
7398 nsAutoString
styleAttrValue(u
"white-space: "_ns
);
7399 styleAttrValue
.Append(
7400 GetWhiteSpaceStyleValue(srcWhiteSpaceStyles
.value()));
7401 IgnoredErrorResult error
;
7402 newSpanElement
->SetAttr(nsGkAtoms::style
, styleAttrValue
, error
);
7403 NS_WARNING_ASSERTION(!error
.Failed(),
7404 "Element::SetAttr(nsGkAtoms::span) failed");
7405 if (MOZ_LIKELY(!error
.Failed())) {
7406 Result
<CreateElementResult
, nsresult
> insertSpanElementResult
=
7407 InsertNodeWithTransaction
<Element
>(*newSpanElement
,
7409 if (MOZ_UNLIKELY(insertSpanElementResult
.isErr())) {
7410 if (NS_WARN_IF(insertSpanElementResult
.inspectErr() ==
7411 NS_ERROR_EDITOR_DESTROYED
)) {
7412 return Err(NS_ERROR_EDITOR_DESTROYED
);
7415 "HTMLEditor::InsertNodeWithTransaction() failed, but ignored");
7417 // We should move the node into the new <span> to preserve the
7419 pointToInsert
.Set(newSpanElement
, 0u);
7420 // We should put caret after aContentToMove after moving it so that
7421 // we do not need the suggested caret point here.
7422 insertSpanElementResult
.inspect().IgnoreCaretPointSuggestion();
7427 // If it can, move it there.
7428 Result
<MoveNodeResult
, nsresult
> moveNodeResult
=
7429 MoveNodeWithTransaction(aContentToMove
, pointToInsert
);
7430 NS_WARNING_ASSERTION(moveNodeResult
.isOk(),
7431 "HTMLEditor::MoveNodeWithTransaction() failed");
7432 // XXX This is odd to override the handled state here, but stopping this
7433 // hits an NS_ASSERTION in WhiteSpaceVisibilityKeeper::
7434 // MergeFirstLineOfRightBlockElementIntoAncestorLeftBlockElement.
7435 if (moveNodeResult
.isOk()) {
7436 MoveNodeResult unwrappedMoveNodeResult
= moveNodeResult
.unwrap();
7437 unwrappedMoveNodeResult
.ForceToMarkAsHandled();
7438 return unwrappedMoveNodeResult
;
7440 return moveNodeResult
;
7443 // If it can't, move its children (if any), and then delete it.
7444 auto moveNodeResult
=
7445 [&]() MOZ_CAN_RUN_SCRIPT
-> Result
<MoveNodeResult
, nsresult
> {
7446 if (!aContentToMove
.IsElement()) {
7447 return MoveNodeResult::HandledResult(aPointToInsert
);
7449 Result
<MoveNodeResult
, nsresult
> moveChildrenResult
=
7450 MoveChildrenWithTransaction(MOZ_KnownLive(*aContentToMove
.AsElement()),
7451 aPointToInsert
, aPreserveWhiteSpaceStyle
,
7452 aRemoveIfCommentNode
);
7453 NS_WARNING_ASSERTION(moveChildrenResult
.isOk(),
7454 "HTMLEditor::MoveChildrenWithTransaction() failed");
7455 return moveChildrenResult
;
7457 if (MOZ_UNLIKELY(moveNodeResult
.isErr())) {
7458 return moveNodeResult
; // Already warned in the lambda.
7461 MoveNodeResult unwrappedMoveNodeResult
= moveNodeResult
.unwrap();
7463 AutoTrackDOMMoveNodeResult
trackMoveNodeResult(RangeUpdaterRef(),
7464 &unwrappedMoveNodeResult
);
7465 nsresult rv
= DeleteNodeWithTransaction(aContentToMove
);
7466 if (NS_FAILED(rv
)) {
7467 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
7468 unwrappedMoveNodeResult
.IgnoreCaretPointSuggestion();
7472 if (!MayHaveMutationEventListeners()) {
7473 return std::move(unwrappedMoveNodeResult
);
7475 // Mutation event listener may make `offset` value invalid with
7476 // removing some previous children while we call
7477 // `DeleteNodeWithTransaction()` so that we should adjust it here.
7478 if (unwrappedMoveNodeResult
.NextInsertionPointRef()
7479 .IsSetAndValidInComposedDoc()) {
7480 return std::move(unwrappedMoveNodeResult
);
7482 unwrappedMoveNodeResult
|= MoveNodeResult::HandledResult(
7483 EditorDOMPoint::AtEndOf(*aPointToInsert
.GetContainer()));
7484 return std::move(unwrappedMoveNodeResult
);
7487 Result
<bool, nsresult
> HTMLEditor::CanMoveChildren(
7488 const Element
& aElement
, const nsINode
& aNewContainer
) const {
7489 if (NS_WARN_IF(&aElement
== &aNewContainer
)) {
7490 return Err(NS_ERROR_FAILURE
);
7492 for (nsIContent
* childContent
= aElement
.GetFirstChild(); childContent
;
7493 childContent
= childContent
->GetNextSibling()) {
7494 Result
<bool, nsresult
> result
=
7495 CanMoveNodeOrChildren(*childContent
, aNewContainer
);
7496 if (result
.isErr() || result
.inspect()) {
7503 Result
<MoveNodeResult
, nsresult
> HTMLEditor::MoveChildrenWithTransaction(
7504 Element
& aElement
, const EditorDOMPoint
& aPointToInsert
,
7505 PreserveWhiteSpaceStyle aPreserveWhiteSpaceStyle
,
7506 RemoveIfCommentNode aRemoveIfCommentNode
) {
7507 MOZ_ASSERT(aPointToInsert
.IsSet());
7509 if (NS_WARN_IF(&aElement
== aPointToInsert
.GetContainer())) {
7510 return Err(NS_ERROR_INVALID_ARG
);
7513 MoveNodeResult moveChildrenResult
=
7514 MoveNodeResult::IgnoredResult(aPointToInsert
);
7515 while (nsCOMPtr
<nsIContent
> firstChild
= aElement
.GetFirstChild()) {
7516 AutoTrackDOMMoveNodeResult
trackMoveChildrenResult(RangeUpdaterRef(),
7517 &moveChildrenResult
);
7518 Result
<MoveNodeResult
, nsresult
> moveNodeOrChildrenResult
=
7519 MoveNodeOrChildrenWithTransaction(
7520 *firstChild
, moveChildrenResult
.NextInsertionPointRef(),
7521 aPreserveWhiteSpaceStyle
, aRemoveIfCommentNode
);
7522 if (MOZ_UNLIKELY(moveNodeOrChildrenResult
.isErr())) {
7523 NS_WARNING("HTMLEditor::MoveNodeOrChildrenWithTransaction() failed");
7524 moveChildrenResult
.IgnoreCaretPointSuggestion();
7525 return moveNodeOrChildrenResult
;
7527 trackMoveChildrenResult
.FlushAndStopTracking();
7528 moveChildrenResult
|= moveNodeOrChildrenResult
.inspect();
7530 return moveChildrenResult
;
7533 nsresult
HTMLEditor::MoveAllChildren(nsINode
& aContainer
,
7534 const EditorRawDOMPoint
& aPointToInsert
) {
7535 if (!aContainer
.HasChildren()) {
7538 nsIContent
* firstChild
= aContainer
.GetFirstChild();
7539 if (NS_WARN_IF(!firstChild
)) {
7540 return NS_ERROR_FAILURE
;
7542 nsIContent
* lastChild
= aContainer
.GetLastChild();
7543 if (NS_WARN_IF(!lastChild
)) {
7544 return NS_ERROR_FAILURE
;
7546 nsresult rv
= MoveChildrenBetween(*firstChild
, *lastChild
, aPointToInsert
);
7547 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
7548 "HTMLEditor::MoveChildrenBetween() failed");
7552 nsresult
HTMLEditor::MoveChildrenBetween(
7553 nsIContent
& aFirstChild
, nsIContent
& aLastChild
,
7554 const EditorRawDOMPoint
& aPointToInsert
) {
7555 nsCOMPtr
<nsINode
> oldContainer
= aFirstChild
.GetParentNode();
7556 if (NS_WARN_IF(oldContainer
!= aLastChild
.GetParentNode()) ||
7557 NS_WARN_IF(!aPointToInsert
.IsInContentNode()) ||
7558 NS_WARN_IF(!aPointToInsert
.CanContainerHaveChildren())) {
7559 return NS_ERROR_INVALID_ARG
;
7562 // First, store all children which should be moved to the new container.
7563 AutoTArray
<nsCOMPtr
<nsIContent
>, 10> children
;
7564 for (nsIContent
* child
= &aFirstChild
; child
;
7565 child
= child
->GetNextSibling()) {
7566 children
.AppendElement(child
);
7567 if (child
== &aLastChild
) {
7572 if (NS_WARN_IF(children
.LastElement() != &aLastChild
)) {
7573 return NS_ERROR_INVALID_ARG
;
7576 nsCOMPtr
<nsIContent
> newContainer
= aPointToInsert
.ContainerAs
<nsIContent
>();
7577 nsCOMPtr
<nsIContent
> nextNode
= aPointToInsert
.GetChild();
7578 IgnoredErrorResult error
;
7579 for (size_t i
= children
.Length(); i
> 0; --i
) {
7580 nsCOMPtr
<nsIContent
>& child
= children
[i
- 1];
7581 if (child
->GetParentNode() != oldContainer
) {
7582 // If the child has been moved to different container, we shouldn't
7586 if (NS_WARN_IF(!HTMLEditUtils::IsRemovableNode(*child
))) {
7587 return NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
;
7589 oldContainer
->RemoveChild(*child
, error
);
7590 if (NS_WARN_IF(Destroyed())) {
7591 return NS_ERROR_EDITOR_DESTROYED
;
7593 if (error
.Failed()) {
7594 NS_WARNING("nsINode::RemoveChild() failed");
7595 return error
.StealNSResult();
7598 // If we're not appending the children to the new container, we should
7599 // check if referring next node of insertion point is still in the new
7601 EditorRawDOMPoint
pointToInsert(nextNode
);
7602 if (NS_WARN_IF(!pointToInsert
.IsSet()) ||
7603 NS_WARN_IF(pointToInsert
.GetContainer() != newContainer
)) {
7604 // The next node of insertion point has been moved by mutation observer.
7605 // Let's stop moving the remaining nodes.
7606 // XXX Or should we move remaining children after the last moved child?
7607 return NS_ERROR_FAILURE
;
7611 newContainer
->IsInComposedDoc() &&
7612 !EditorUtils::IsEditableContent(*newContainer
, EditorType::HTML
))) {
7613 return NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
;
7615 newContainer
->InsertBefore(*child
, nextNode
, error
);
7616 if (NS_WARN_IF(Destroyed())) {
7617 return NS_ERROR_EDITOR_DESTROYED
;
7619 if (error
.Failed()) {
7620 NS_WARNING("nsINode::InsertBefore() failed");
7621 return error
.StealNSResult();
7623 // If the child was inserted or appended properly, the following children
7624 // should be inserted before it. Otherwise, keep using current position.
7625 if (child
->GetParentNode() == newContainer
) {
7632 nsresult
HTMLEditor::MovePreviousSiblings(
7633 nsIContent
& aChild
, const EditorRawDOMPoint
& aPointToInsert
) {
7634 if (NS_WARN_IF(!aChild
.GetParentNode())) {
7635 return NS_ERROR_INVALID_ARG
;
7637 nsIContent
* firstChild
= aChild
.GetParentNode()->GetFirstChild();
7638 if (NS_WARN_IF(!firstChild
)) {
7639 return NS_ERROR_FAILURE
;
7641 nsIContent
* lastChild
=
7642 &aChild
== firstChild
? firstChild
: aChild
.GetPreviousSibling();
7643 if (NS_WARN_IF(!lastChild
)) {
7644 return NS_ERROR_FAILURE
;
7646 nsresult rv
= MoveChildrenBetween(*firstChild
, *lastChild
, aPointToInsert
);
7647 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
7648 "HTMLEditor::MoveChildrenBetween() failed");
7652 nsresult
HTMLEditor::MoveInclusiveNextSiblings(
7653 nsIContent
& aChild
, const EditorRawDOMPoint
& aPointToInsert
) {
7654 if (NS_WARN_IF(!aChild
.GetParentNode())) {
7655 return NS_ERROR_INVALID_ARG
;
7657 nsIContent
* lastChild
= aChild
.GetParentNode()->GetLastChild();
7658 if (NS_WARN_IF(!lastChild
)) {
7659 return NS_ERROR_FAILURE
;
7661 nsresult rv
= MoveChildrenBetween(aChild
, *lastChild
, aPointToInsert
);
7662 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
7663 "HTMLEditor::MoveChildrenBetween() failed");
7667 Result
<DeleteRangeResult
, nsresult
> HTMLEditor::AutoDeleteRangesHandler::
7668 AutoBlockElementsJoiner::DeleteContentButKeepTableStructure(
7669 HTMLEditor
& aHTMLEditor
, nsIContent
& aContent
) {
7670 MOZ_ASSERT(aHTMLEditor
.IsEditActionDataAvailable());
7672 if (!HTMLEditUtils::IsAnyTableElementButNotTable(&aContent
)) {
7673 nsCOMPtr
<nsINode
> parentNode
= aContent
.GetParentNode();
7674 if (NS_WARN_IF(!parentNode
)) {
7675 return Err(NS_ERROR_FAILURE
);
7677 nsCOMPtr
<nsIContent
> nextSibling
= aContent
.GetNextSibling();
7678 nsresult rv
= aHTMLEditor
.DeleteNodeWithTransaction(aContent
);
7679 if (NS_FAILED(rv
)) {
7680 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
7683 if (NS_WARN_IF(nextSibling
&& nextSibling
->GetParentNode() != parentNode
) ||
7684 NS_WARN_IF(!parentNode
->IsInComposedDoc())) {
7685 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
);
7687 return DeleteRangeResult(
7688 EditorDOMRange(nextSibling
? EditorDOMPoint(nextSibling
)
7689 : EditorDOMPoint::AtEndOf(*parentNode
)),
7693 // XXX For performance, this should just call
7694 // DeleteContentButKeepTableStructure() while there are children in
7695 // aContent. If we need to avoid infinite loop because mutation event
7696 // listeners can add unexpected nodes into aContent, we should just loop
7697 // only original count of the children.
7698 AutoTArray
<OwningNonNull
<nsIContent
>, 10> childList
;
7699 for (nsIContent
* child
= aContent
.GetFirstChild(); child
;
7700 child
= child
->GetNextSibling()) {
7701 childList
.AppendElement(*child
);
7704 for (const auto& child
: childList
) {
7705 // MOZ_KnownLive because 'childList' is guaranteed to
7707 Result
<DeleteRangeResult
, nsresult
> deleteChildResult
=
7708 DeleteContentButKeepTableStructure(aHTMLEditor
, MOZ_KnownLive(child
));
7709 if (MOZ_UNLIKELY(deleteChildResult
.isErr())) {
7710 NS_WARNING("HTMLEditor::DeleteContentButKeepTableStructure() failed");
7711 return deleteChildResult
.propagateErr();
7713 deleteChildResult
.unwrap().IgnoreCaretPointSuggestion();
7715 if (NS_WARN_IF(!aContent
.IsInComposedDoc())) {
7716 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
);
7719 // Insert a <br> into new empty table cell or caption because we don't have a
7720 // change to do it for the middle of the range. Note that this does not
7721 // handle first cell/caption and end cell/caption at the deleting range. They
7722 // should be handled by upper level because we may need to delete unnecessary
7723 // new empty inline ancestors in the cells/captions.
7724 if (!HTMLEditUtils::IsTableCellOrCaption(aContent
) ||
7725 aContent
.GetChildCount()) {
7726 return DeleteRangeResult(EditorDOMRange(EditorDOMPoint(&aContent
, 0u),
7727 EditorDOMPoint::AtEndOf(aContent
)),
7730 Result
<CreateLineBreakResult
, nsresult
> insertLineBreakResultOrError
=
7731 aHTMLEditor
.InsertLineBreak(WithTransaction::Yes
,
7732 LineBreakType::BRElement
,
7733 EditorDOMPoint(&aContent
, 0));
7734 if (MOZ_UNLIKELY(insertLineBreakResultOrError
.isErr())) {
7736 "HTMLEditor::InsertLineBreak(WithTransaction::Yes, "
7737 "LineBreakType::BRElement) failed");
7738 return insertLineBreakResultOrError
.propagateErr();
7740 CreateLineBreakResult insertLineBreakResult
=
7741 insertLineBreakResultOrError
.unwrap();
7742 insertLineBreakResult
.IgnoreCaretPointSuggestion();
7743 return DeleteRangeResult(EditorDOMRange(EditorDOMPoint(&aContent
, 0u)),
7747 nsresult
HTMLEditor::DeleteMostAncestorMailCiteElementIfEmpty(
7748 nsIContent
& aContent
) {
7749 MOZ_ASSERT(IsEditActionDataAvailable());
7751 // The element must be `<blockquote type="cite">` or
7752 // `<span _moz_quote="true">`.
7753 RefPtr
<Element
> mailCiteElement
=
7754 GetMostDistantAncestorMailCiteElement(aContent
);
7755 if (!mailCiteElement
) {
7758 bool seenBR
= false;
7759 if (!HTMLEditUtils::IsEmptyNode(
7761 {EmptyCheckOption::TreatListItemAsVisible
,
7762 EmptyCheckOption::TreatTableCellAsVisible
,
7763 EmptyCheckOption::TreatNonEditableContentAsInvisible
},
7767 EditorDOMPoint
atEmptyMailCiteElement(mailCiteElement
);
7769 AutoEditorDOMPointChildInvalidator
lockOffset(atEmptyMailCiteElement
);
7770 nsresult rv
= DeleteNodeWithTransaction(*mailCiteElement
);
7771 if (NS_FAILED(rv
)) {
7772 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
7777 if (!atEmptyMailCiteElement
.IsSet() || !seenBR
) {
7778 NS_WARNING_ASSERTION(
7779 atEmptyMailCiteElement
.IsSet(),
7780 "Mutation event listener might changed the DOM tree during "
7781 "EditorBase::DeleteNodeWithTransaction(), but ignored");
7785 Result
<CreateLineBreakResult
, nsresult
> insertBRElementResultOrError
=
7786 InsertLineBreak(WithTransaction::Yes
, LineBreakType::BRElement
,
7787 atEmptyMailCiteElement
);
7788 if (MOZ_UNLIKELY(insertBRElementResultOrError
.isErr())) {
7790 "HTMLEditor::InsertLineBreak(WithTransaction::Yes, "
7791 "LineBreakType::BRElement) failed");
7792 return insertBRElementResultOrError
.unwrapErr();
7794 CreateLineBreakResult insertBRElementResult
=
7795 insertBRElementResultOrError
.unwrap();
7796 MOZ_ASSERT(insertBRElementResult
.Handled());
7797 nsresult rv
= insertBRElementResult
.SuggestCaretPointTo(
7798 *this, {SuggestCaret::AndIgnoreTrivialError
});
7799 if (NS_FAILED(rv
)) {
7800 NS_WARNING("CaretPoint::SuggestCaretPointTo() failed");
7803 NS_WARNING_ASSERTION(rv
== NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR
,
7804 "CaretPoint::SuggestCaretPointTo() failed, but ignored");
7808 Element
* HTMLEditor::AutoDeleteRangesHandler::AutoEmptyBlockAncestorDeleter::
7809 ScanEmptyBlockInclusiveAncestor(const HTMLEditor
& aHTMLEditor
,
7810 nsIContent
& aStartContent
) {
7811 MOZ_ASSERT(aHTMLEditor
.IsEditActionDataAvailable());
7812 MOZ_ASSERT(!mEmptyInclusiveAncestorBlockElement
);
7814 // If we are inside an empty block, delete it.
7815 // Note: do NOT delete table elements this way.
7816 // Note: do NOT delete non-editable block element.
7817 Element
* editableBlockElement
= HTMLEditUtils::GetInclusiveAncestorElement(
7818 aStartContent
, HTMLEditUtils::ClosestEditableBlockElement
,
7819 BlockInlineCheck::UseComputedDisplayOutsideStyle
);
7820 if (!editableBlockElement
) {
7823 // XXX Perhaps, this is slow loop. If empty blocks are nested, then,
7824 // each block checks whether it's empty or not. However, descendant
7825 // blocks are checked again and again by IsEmptyNode(). Perhaps, it
7826 // should be able to take "known empty element" for avoiding same checks.
7827 while (editableBlockElement
&&
7828 HTMLEditUtils::IsRemovableFromParentNode(*editableBlockElement
) &&
7829 !HTMLEditUtils::IsAnyTableElement(editableBlockElement
) &&
7830 HTMLEditUtils::IsEmptyNode(*editableBlockElement
)) {
7831 // If the removable empty list item is a child of editing host list element,
7832 // we should not delete it.
7833 if (HTMLEditUtils::IsListItem(editableBlockElement
)) {
7834 Element
* const parentElement
= editableBlockElement
->GetParentElement();
7835 if (parentElement
&& HTMLEditUtils::IsAnyListElement(parentElement
) &&
7836 !HTMLEditUtils::IsRemovableFromParentNode(*parentElement
) &&
7837 HTMLEditUtils::IsEmptyNode(*parentElement
)) {
7841 mEmptyInclusiveAncestorBlockElement
= editableBlockElement
;
7842 editableBlockElement
= HTMLEditUtils::GetAncestorElement(
7843 *mEmptyInclusiveAncestorBlockElement
,
7844 HTMLEditUtils::ClosestEditableBlockElement
,
7845 BlockInlineCheck::UseComputedDisplayOutsideStyle
);
7847 if (!mEmptyInclusiveAncestorBlockElement
) {
7851 // XXX Because of not checking whether found block element is editable
7852 // in the above loop, empty ediable block element may be overwritten
7853 // with empty non-editable clock element. Therefore, we fail to
7854 // remove the found empty nodes.
7855 if (NS_WARN_IF(!mEmptyInclusiveAncestorBlockElement
->IsEditable()) ||
7856 NS_WARN_IF(!mEmptyInclusiveAncestorBlockElement
->GetParentElement())) {
7857 mEmptyInclusiveAncestorBlockElement
= nullptr;
7859 return mEmptyInclusiveAncestorBlockElement
;
7862 nsresult
HTMLEditor::AutoDeleteRangesHandler::AutoEmptyBlockAncestorDeleter::
7863 ComputeTargetRanges(const HTMLEditor
& aHTMLEditor
,
7864 nsIEditor::EDirection aDirectionAndAmount
,
7865 const Element
& aEditingHost
,
7866 AutoClonedSelectionRangeArray
& aRangesToDelete
) const {
7867 MOZ_ASSERT(mEmptyInclusiveAncestorBlockElement
);
7869 // We'll delete `mEmptyInclusiveAncestorBlockElement` node from the tree, but
7870 // we should return the range from start/end of next/previous editable content
7871 // to end/start of the element for compatiblity with the other browsers.
7872 switch (aDirectionAndAmount
) {
7873 case nsIEditor::eNone
:
7875 case nsIEditor::ePrevious
:
7876 case nsIEditor::ePreviousWord
:
7877 case nsIEditor::eToBeginningOfLine
: {
7878 EditorRawDOMPoint startPoint
=
7879 HTMLEditUtils::GetPreviousEditablePoint
<EditorRawDOMPoint
>(
7880 *mEmptyInclusiveAncestorBlockElement
, &aEditingHost
,
7881 // In this case, we don't join block elements so that we won't
7882 // delete invisible trailing whitespaces in the previous element.
7883 InvisibleWhiteSpaces::Preserve
,
7884 // In this case, we won't join table cells so that we should
7885 // get a range which is in a table cell even if it's in a
7887 TableBoundary::NoCrossAnyTableElement
);
7888 if (!startPoint
.IsSet()) {
7890 "HTMLEditUtils::GetPreviousEditablePoint() didn't return a valid "
7892 return NS_ERROR_FAILURE
;
7894 nsresult rv
= aRangesToDelete
.SetStartAndEnd(
7896 EditorRawDOMPoint::AtEndOf(mEmptyInclusiveAncestorBlockElement
));
7897 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
7898 "AutoClonedRangeArray::SetStartAndEnd() failed");
7901 case nsIEditor::eNext
:
7902 case nsIEditor::eNextWord
:
7903 case nsIEditor::eToEndOfLine
: {
7904 EditorRawDOMPoint endPoint
=
7905 HTMLEditUtils::GetNextEditablePoint
<EditorRawDOMPoint
>(
7906 *mEmptyInclusiveAncestorBlockElement
, &aEditingHost
,
7907 // In this case, we don't join block elements so that we won't
7908 // delete invisible trailing whitespaces in the next element.
7909 InvisibleWhiteSpaces::Preserve
,
7910 // In this case, we won't join table cells so that we should
7911 // get a range which is in a table cell even if it's in a
7913 TableBoundary::NoCrossAnyTableElement
);
7914 if (!endPoint
.IsSet()) {
7916 "HTMLEditUtils::GetNextEditablePoint() didn't return a valid "
7918 return NS_ERROR_FAILURE
;
7920 nsresult rv
= aRangesToDelete
.SetStartAndEnd(
7921 EditorRawDOMPoint(mEmptyInclusiveAncestorBlockElement
, 0), endPoint
);
7922 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
7923 "AutoClonedRangeArray::SetStartAndEnd() failed");
7927 MOZ_ASSERT_UNREACHABLE("Handle the nsIEditor::EDirection value");
7930 // No direction, let's select the element to be deleted.
7932 aRangesToDelete
.SelectNode(*mEmptyInclusiveAncestorBlockElement
);
7933 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
7934 "AutoClonedRangeArray::SelectNode() failed");
7938 Result
<CreateLineBreakResult
, nsresult
>
7939 HTMLEditor::AutoDeleteRangesHandler::AutoEmptyBlockAncestorDeleter::
7940 MaybeInsertBRElementBeforeEmptyListItemElement(HTMLEditor
& aHTMLEditor
) {
7941 MOZ_ASSERT(mEmptyInclusiveAncestorBlockElement
);
7942 MOZ_ASSERT(mEmptyInclusiveAncestorBlockElement
->GetParentElement());
7943 MOZ_ASSERT(HTMLEditUtils::IsListItem(mEmptyInclusiveAncestorBlockElement
));
7945 // If the found empty block is a list item element and its grand parent
7946 // (i.e., parent of list element) is NOT a list element, insert <br>
7947 // element before the list element which has the empty list item.
7948 // This odd list structure may occur if `Document.execCommand("indent")`
7949 // is performed for list items.
7950 // XXX Chrome does not remove empty list elements when last content in
7951 // last list item is deleted. We should follow it since current
7952 // behavior is annoying when you type new list item with selecting
7954 if (!HTMLEditUtils::IsFirstChild(*mEmptyInclusiveAncestorBlockElement
,
7955 {WalkTreeOption::IgnoreNonEditableNode
})) {
7956 return CreateLineBreakResult::NotHandled();
7959 const EditorDOMPoint
atParentOfEmptyListItem(
7960 mEmptyInclusiveAncestorBlockElement
->GetParentElement());
7961 if (NS_WARN_IF(!atParentOfEmptyListItem
.IsSet())) {
7962 return Err(NS_ERROR_FAILURE
);
7964 if (HTMLEditUtils::IsAnyListElement(atParentOfEmptyListItem
.GetContainer())) {
7965 return CreateLineBreakResult::NotHandled();
7967 Result
<CreateLineBreakResult
, nsresult
> insertBRElementResultOrError
=
7968 aHTMLEditor
.InsertLineBreak(WithTransaction::Yes
,
7969 LineBreakType::BRElement
,
7970 atParentOfEmptyListItem
);
7971 if (MOZ_UNLIKELY(insertBRElementResultOrError
.isErr())) {
7973 "HTMLEditor::InsertLineBreak(WithTransaction::Yes, "
7974 "LineBreakType::BRElement) failed");
7975 return insertBRElementResultOrError
.propagateErr();
7977 CreateLineBreakResult insertBRElementResult
=
7978 insertBRElementResultOrError
.unwrap();
7979 nsresult rv
= insertBRElementResult
.SuggestCaretPointTo(
7980 aHTMLEditor
, {SuggestCaret::OnlyIfHasSuggestion
,
7981 SuggestCaret::OnlyIfTransactionsAllowedToDoIt
,
7982 SuggestCaret::AndIgnoreTrivialError
});
7983 if (NS_FAILED(rv
)) {
7984 NS_WARNING("CaretPoint::SuggestCaretPointTo() failed");
7987 MOZ_ASSERT(insertBRElementResult
.Handled());
7988 return std::move(insertBRElementResult
);
7991 Result
<CaretPoint
, nsresult
> HTMLEditor::AutoDeleteRangesHandler::
7992 AutoEmptyBlockAncestorDeleter::GetNewCaretPosition(
7993 const HTMLEditor
& aHTMLEditor
,
7994 nsIEditor::EDirection aDirectionAndAmount
) const {
7995 MOZ_ASSERT(mEmptyInclusiveAncestorBlockElement
);
7996 MOZ_ASSERT(mEmptyInclusiveAncestorBlockElement
->GetParentElement());
7997 MOZ_ASSERT(aHTMLEditor
.IsEditActionDataAvailable());
7999 switch (aDirectionAndAmount
) {
8000 case nsIEditor::eNext
:
8001 case nsIEditor::eNextWord
:
8002 case nsIEditor::eToEndOfLine
: {
8003 // Collapse Selection to next node of after empty block element
8004 // if there is. Otherwise, to just after the empty block.
8005 auto afterEmptyBlock(
8006 EditorDOMPoint::After(mEmptyInclusiveAncestorBlockElement
));
8007 MOZ_ASSERT(afterEmptyBlock
.IsSet());
8008 if (nsIContent
* nextContentOfEmptyBlock
= HTMLEditUtils::GetNextContent(
8009 afterEmptyBlock
, {}, BlockInlineCheck::Unused
,
8010 aHTMLEditor
.ComputeEditingHost())) {
8011 EditorDOMPoint pt
= HTMLEditUtils::GetGoodCaretPointFor
<EditorDOMPoint
>(
8012 *nextContentOfEmptyBlock
, aDirectionAndAmount
);
8014 NS_WARNING("HTMLEditUtils::GetGoodCaretPointFor() failed");
8015 return Err(NS_ERROR_FAILURE
);
8017 return CaretPoint(std::move(pt
));
8019 if (NS_WARN_IF(!afterEmptyBlock
.IsSet())) {
8020 return Err(NS_ERROR_FAILURE
);
8022 return CaretPoint(std::move(afterEmptyBlock
));
8024 case nsIEditor::ePrevious
:
8025 case nsIEditor::ePreviousWord
:
8026 case nsIEditor::eToBeginningOfLine
: {
8027 // Collapse Selection to previous editable node of the empty block
8028 // if there is. Otherwise, to after the empty block.
8029 EditorRawDOMPoint
atEmptyBlock(mEmptyInclusiveAncestorBlockElement
);
8030 if (nsIContent
* previousContentOfEmptyBlock
=
8031 HTMLEditUtils::GetPreviousContent(
8032 atEmptyBlock
, {WalkTreeOption::IgnoreNonEditableNode
},
8033 BlockInlineCheck::Unused
, aHTMLEditor
.ComputeEditingHost())) {
8034 EditorDOMPoint pt
= HTMLEditUtils::GetGoodCaretPointFor
<EditorDOMPoint
>(
8035 *previousContentOfEmptyBlock
, aDirectionAndAmount
);
8037 NS_WARNING("HTMLEditUtils::GetGoodCaretPointFor() failed");
8038 return Err(NS_ERROR_FAILURE
);
8040 return CaretPoint(std::move(pt
));
8042 auto afterEmptyBlock
=
8043 EditorDOMPoint::After(*mEmptyInclusiveAncestorBlockElement
);
8044 if (NS_WARN_IF(!afterEmptyBlock
.IsSet())) {
8045 return Err(NS_ERROR_FAILURE
);
8047 return CaretPoint(std::move(afterEmptyBlock
));
8049 case nsIEditor::eNone
: {
8050 // Collapse selection at the removing block when we are replacing
8051 // selected content.
8052 EditorDOMPoint
atEmptyBlock(mEmptyInclusiveAncestorBlockElement
);
8053 if (NS_WARN_IF(!atEmptyBlock
.IsSet())) {
8054 return Err(NS_ERROR_FAILURE
);
8056 return CaretPoint(std::move(atEmptyBlock
));
8060 "AutoEmptyBlockAncestorDeleter doesn't support this action yet");
8061 return Err(NS_ERROR_FAILURE
);
8065 Result
<DeleteRangeResult
, nsresult
>
8066 HTMLEditor::AutoDeleteRangesHandler::AutoEmptyBlockAncestorDeleter::Run(
8067 HTMLEditor
& aHTMLEditor
, nsIEditor::EDirection aDirectionAndAmount
,
8068 const Element
& aEditingHost
) {
8069 MOZ_ASSERT(mEmptyInclusiveAncestorBlockElement
);
8070 MOZ_ASSERT(mEmptyInclusiveAncestorBlockElement
->GetParentElement());
8071 MOZ_ASSERT(aHTMLEditor
.IsEditActionDataAvailable());
8074 Result
<DeleteRangeResult
, nsresult
> replaceSubListResultOrError
=
8075 MaybeReplaceSubListWithNewListItem(aHTMLEditor
);
8076 if (MOZ_UNLIKELY(replaceSubListResultOrError
.isErr())) {
8078 "AutoEmptyBlockAncestorDeleter::MaybeReplaceSubListWithNewListItem() "
8080 return replaceSubListResultOrError
.propagateErr();
8082 if (replaceSubListResultOrError
.inspect().Handled()) {
8083 return replaceSubListResultOrError
;
8087 auto caretPointOrError
= [&]() MOZ_CAN_RUN_SCRIPT MOZ_NEVER_INLINE_DEBUG
8088 -> Result
<CaretPoint
, nsresult
> {
8089 if (HTMLEditUtils::IsListItem(mEmptyInclusiveAncestorBlockElement
)) {
8090 Result
<CreateLineBreakResult
, nsresult
> insertBRElementResultOrError
=
8091 MaybeInsertBRElementBeforeEmptyListItemElement(aHTMLEditor
);
8092 if (MOZ_UNLIKELY(insertBRElementResultOrError
.isErr())) {
8094 "AutoEmptyBlockAncestorDeleter::"
8095 "MaybeInsertBRElementBeforeEmptyListItemElement() failed");
8096 return insertBRElementResultOrError
.propagateErr();
8098 CreateLineBreakResult insertBRElementResult
=
8099 insertBRElementResultOrError
.unwrap();
8100 // If a `<br>` element is inserted, caret should be moved to after it.
8101 // XXX This comment is wrong, we're suggesting the line break position...
8102 MOZ_ASSERT_IF(insertBRElementResult
.Handled(),
8103 insertBRElementResult
->IsHTMLBRElement());
8104 insertBRElementResult
.IgnoreCaretPointSuggestion();
8106 insertBRElementResult
.Handled()
8107 ? insertBRElementResult
.AtLineBreak
<EditorDOMPoint
>()
8108 : EditorDOMPoint());
8110 Result
<CaretPoint
, nsresult
> caretPointOrError
=
8111 GetNewCaretPosition(aHTMLEditor
, aDirectionAndAmount
);
8112 NS_WARNING_ASSERTION(
8113 caretPointOrError
.isOk(),
8114 "AutoEmptyBlockAncestorDeleter::GetNewCaretPosition() failed");
8115 MOZ_ASSERT_IF(caretPointOrError
.isOk(),
8116 caretPointOrError
.inspect().HasCaretPointSuggestion());
8117 return caretPointOrError
;
8119 if (MOZ_UNLIKELY(caretPointOrError
.isErr())) {
8120 return caretPointOrError
.propagateErr();
8122 EditorDOMPoint pointToPutCaret
=
8123 caretPointOrError
.unwrap().UnwrapCaretPoint();
8124 const bool unwrapAncestorBlocks
=
8125 !HTMLEditUtils::IsListItem(mEmptyInclusiveAncestorBlockElement
) &&
8126 pointToPutCaret
.GetContainer() ==
8127 mEmptyInclusiveAncestorBlockElement
->GetParentNode();
8128 nsCOMPtr
<nsINode
> parentNode
=
8129 mEmptyInclusiveAncestorBlockElement
->GetParentNode();
8130 nsCOMPtr
<nsIContent
> nextSibling
=
8131 mEmptyInclusiveAncestorBlockElement
->GetNextSibling();
8133 AutoTrackDOMPoint
trackPointToPutCaret(aHTMLEditor
.RangeUpdaterRef(),
8135 nsresult rv
= aHTMLEditor
.DeleteNodeWithTransaction(
8136 MOZ_KnownLive(*mEmptyInclusiveAncestorBlockElement
));
8137 if (NS_FAILED(rv
)) {
8138 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
8141 if (NS_WARN_IF(!parentNode
->IsInComposedDoc()) ||
8142 NS_WARN_IF(nextSibling
&& nextSibling
->GetParentNode() != parentNode
)) {
8143 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
);
8146 auto pointToInsertLineBreak
= nextSibling
8147 ? EditorDOMPoint(nextSibling
)
8148 : EditorDOMPoint::AtEndOf(*parentNode
);
8149 DeleteRangeResult
deleteNodeResult(pointToInsertLineBreak
,
8150 std::move(pointToPutCaret
));
8151 if ((aHTMLEditor
.IsMailEditor() || aHTMLEditor
.IsPlaintextMailComposer()) &&
8152 MOZ_LIKELY(pointToInsertLineBreak
.IsInContentNode())) {
8153 AutoTrackDOMDeleteRangeResult
trackDeleteNodeResult(
8154 aHTMLEditor
.RangeUpdaterRef(), &deleteNodeResult
);
8155 AutoTrackDOMPoint
trackPointToInsertLineBreak(aHTMLEditor
.RangeUpdaterRef(),
8156 &pointToInsertLineBreak
);
8157 nsresult rv
= aHTMLEditor
.DeleteMostAncestorMailCiteElementIfEmpty(
8158 MOZ_KnownLive(*pointToInsertLineBreak
.ContainerAs
<nsIContent
>()));
8159 if (NS_FAILED(rv
)) {
8161 "HTMLEditor::DeleteMostAncestorMailCiteElementIfEmpty() failed");
8162 deleteNodeResult
.IgnoreCaretPointSuggestion();
8165 trackPointToInsertLineBreak
.FlushAndStopTracking();
8166 if (NS_WARN_IF(!pointToInsertLineBreak
.IsSetAndValidInComposedDoc())) {
8167 deleteNodeResult
.IgnoreCaretPointSuggestion();
8168 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
);
8170 trackDeleteNodeResult
.FlushAndStopTracking();
8171 deleteNodeResult
|= DeleteRangeResult(
8172 EditorDOMRange(pointToInsertLineBreak
), EditorDOMPoint());
8174 if (unwrapAncestorBlocks
&& aHTMLEditor
.GetTopLevelEditSubAction() ==
8175 EditSubAction::eDeleteSelectedContent
) {
8176 AutoTrackDOMDeleteRangeResult
trackDeleteNodeResult(
8177 aHTMLEditor
.RangeUpdaterRef(), &deleteNodeResult
);
8178 Result
<CreateLineBreakResult
, nsresult
> insertPaddingBRElementOrError
=
8179 aHTMLEditor
.InsertPaddingBRElementIfNeeded(
8180 pointToInsertLineBreak
,
8181 aEditingHost
.IsContentEditablePlainTextOnly() ? nsIEditor::eNoStrip
8182 : nsIEditor::eStrip
,
8184 if (MOZ_UNLIKELY(insertPaddingBRElementOrError
.isErr())) {
8185 NS_WARNING("HTMLEditor::InsertPaddingBRElementIfNeeded() failed");
8186 deleteNodeResult
.IgnoreCaretPointSuggestion();
8187 return insertPaddingBRElementOrError
.propagateErr();
8189 insertPaddingBRElementOrError
.unwrap().IgnoreCaretPointSuggestion();
8191 MOZ_ASSERT(deleteNodeResult
.Handled());
8192 return std::move(deleteNodeResult
);
8195 Result
<DeleteRangeResult
, nsresult
> HTMLEditor::AutoDeleteRangesHandler::
8196 AutoEmptyBlockAncestorDeleter::MaybeReplaceSubListWithNewListItem(
8197 HTMLEditor
& aHTMLEditor
) {
8198 // If we're deleting sublist element and it's the last list item of its parent
8199 // list, we should replace it with a list element.
8200 if (!HTMLEditUtils::IsAnyListElement(mEmptyInclusiveAncestorBlockElement
)) {
8201 return DeleteRangeResult::IgnoredResult();
8203 const RefPtr
<Element
> parentElement
=
8204 mEmptyInclusiveAncestorBlockElement
->GetParentElement();
8205 if (!parentElement
|| !HTMLEditUtils::IsAnyListElement(parentElement
) ||
8206 !HTMLEditUtils::IsEmptyNode(
8208 {EmptyCheckOption::TreatNonEditableContentAsInvisible
})) {
8209 return DeleteRangeResult::IgnoredResult();
8212 const nsCOMPtr
<nsINode
> nextSibling
=
8213 mEmptyInclusiveAncestorBlockElement
->GetNextSibling();
8214 nsresult rv
= aHTMLEditor
.DeleteNodeWithTransaction(
8215 MOZ_KnownLive(*mEmptyInclusiveAncestorBlockElement
));
8216 if (NS_FAILED(rv
)) {
8217 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
8220 if (NS_WARN_IF(nextSibling
&&
8221 nextSibling
->GetParentNode() != parentElement
) ||
8222 NS_WARN_IF(!parentElement
->IsInComposedDoc())) {
8223 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
);
8225 const auto pointAtDeletedNode
= nextSibling
8226 ? EditorDOMPoint(nextSibling
)
8227 : EditorDOMPoint::AtEndOf(*parentElement
);
8228 auto deleteNodeResult
=
8229 DeleteRangeResult(EditorDOMRange(pointAtDeletedNode
), EditorDOMPoint());
8230 AutoTrackDOMDeleteRangeResult
trackDeleteNodeResult(
8231 aHTMLEditor
.RangeUpdaterRef(), &deleteNodeResult
);
8232 Result
<CreateElementResult
, nsresult
> insertListItemResultOrError
=
8233 aHTMLEditor
.CreateAndInsertElement(
8234 WithTransaction::Yes
,
8235 parentElement
->IsHTMLElement(nsGkAtoms::dl
) ? *nsGkAtoms::dd
8238 [](HTMLEditor
& aHTMLEditor
, Element
& aNewElement
,
8239 const EditorDOMPoint
& aPointToInsert
) -> nsresult
{
8240 RefPtr
<Element
> brElement
=
8241 aHTMLEditor
.CreateHTMLContent(nsGkAtoms::br
);
8242 if (MOZ_UNLIKELY(!brElement
)) {
8244 "EditorBase::CreateHTMLContent(nsGkAtoms::br) failed, but "
8246 return NS_OK
; // Just gives up to insert <br>
8248 IgnoredErrorResult error
;
8249 aNewElement
.AppendChild(*brElement
, error
);
8250 NS_WARNING_ASSERTION(!error
.Failed(),
8251 "nsINode::AppendChild() failed, but ignored");
8254 if (MOZ_UNLIKELY(insertListItemResultOrError
.isErr())) {
8255 NS_WARNING("HTMLEditor::CreateAndInsertElement() failed");
8256 deleteNodeResult
.IgnoreCaretPointSuggestion();
8257 return insertListItemResultOrError
.propagateErr();
8259 trackDeleteNodeResult
.FlushAndStopTracking();
8260 CreateElementResult insertListItemResult
=
8261 insertListItemResultOrError
.unwrap();
8262 insertListItemResult
.IgnoreCaretPointSuggestion();
8264 CaretPoint(EditorDOMPoint(insertListItemResult
.GetNewNode(), 0u));
8265 MOZ_ASSERT(deleteNodeResult
.Handled());
8266 return std::move(deleteNodeResult
);
8269 template <typename EditorDOMRangeType
>
8270 Result
<EditorRawDOMRange
, nsresult
>
8271 HTMLEditor::AutoDeleteRangesHandler::ExtendOrShrinkRangeToDelete(
8272 const HTMLEditor
& aHTMLEditor
,
8273 const LimitersAndCaretData
& aLimitersAndCaretData
,
8274 const EditorDOMRangeType
& aRangeToDelete
) const {
8275 MOZ_ASSERT(aHTMLEditor
.IsEditActionDataAvailable());
8276 MOZ_ASSERT(!aRangeToDelete
.Collapsed());
8277 MOZ_ASSERT(aRangeToDelete
.IsPositioned());
8279 const nsIContent
* commonAncestor
= nsIContent::FromNodeOrNull(
8280 nsContentUtils::GetClosestCommonInclusiveAncestor(
8281 aRangeToDelete
.StartRef().GetContainer(),
8282 aRangeToDelete
.EndRef().GetContainer()));
8283 if (MOZ_UNLIKELY(NS_WARN_IF(!commonAncestor
))) {
8284 return Err(NS_ERROR_FAILURE
);
8287 // Editing host may be nested and outer one could have focus. Let's use
8288 // the closest editing host instead.
8289 const RefPtr
<Element
> closestEditingHost
=
8290 aHTMLEditor
.ComputeEditingHost(*commonAncestor
, LimitInBodyElement::No
);
8291 if (NS_WARN_IF(!closestEditingHost
)) {
8292 return Err(NS_ERROR_FAILURE
);
8295 // Look for the common ancestor's block element in the editing host. It's
8296 // fine that we get non-editable block element which is ancestor of inline
8297 // editing host because the following code checks editing host too.
8298 const RefPtr
<Element
> closestBlockAncestorOrInlineEditingHost
= [&]() {
8299 // Note that if non-closest editing host has focus, found block may be
8301 if (Element
* const maybeEditableBlockElement
=
8302 HTMLEditUtils::GetInclusiveAncestorElement(
8303 *commonAncestor
, HTMLEditUtils::ClosestBlockElement
,
8304 BlockInlineCheck::UseComputedDisplayOutsideStyle
,
8305 closestEditingHost
)) {
8306 return maybeEditableBlockElement
;
8308 return closestEditingHost
.get();
8311 // Set up for loops and cache our root element
8312 // If only one list element is selected, and if the list element is empty,
8313 // we should delete only the list element. Or if the list element is not
8314 // empty, we should make the list has only one empty list item element.
8315 if (const Element
* maybeListElement
=
8316 HTMLEditUtils::GetElementIfOnlyOneSelected(aRangeToDelete
)) {
8317 if (HTMLEditUtils::IsAnyListElement(maybeListElement
) &&
8318 !HTMLEditUtils::IsEmptyAnyListElement(*maybeListElement
)) {
8319 EditorRawDOMRange range
=
8320 HTMLEditUtils::GetRangeSelectingAllContentInAllListItems
<
8321 EditorRawDOMRange
>(*maybeListElement
);
8322 if (range
.IsPositioned()) {
8323 if (EditorUtils::IsEditableContent(
8324 *range
.StartRef().ContainerAs
<nsIContent
>(),
8325 EditorType::HTML
) &&
8326 EditorUtils::IsEditableContent(
8327 *range
.EndRef().ContainerAs
<nsIContent
>(), EditorType::HTML
)) {
8331 // If the first and/or last list item is not editable, we need to do more
8332 // complicated things probably, but we just delete the list element with
8333 // invisible things around it for now since it must be rare case.
8335 // Otherwise, if the list item is empty, we should delete it with invisible
8336 // things around it.
8339 // Find previous visible things before start of selection
8340 EditorRawDOMRange
rangeToDelete(aRangeToDelete
);
8341 if (rangeToDelete
.StartRef().GetContainer() !=
8342 closestBlockAncestorOrInlineEditingHost
) {
8344 const WSScanResult backwardScanFromStartResult
=
8345 WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary(
8346 closestEditingHost
, rangeToDelete
.StartRef(),
8347 BlockInlineCheck::UseComputedDisplayOutsideStyle
);
8348 if (!backwardScanFromStartResult
.ReachedCurrentBlockBoundary() &&
8349 !backwardScanFromStartResult
.ReachedInlineEditingHostBoundary()) {
8352 MOZ_ASSERT(backwardScanFromStartResult
.GetContent() ==
8353 WSRunScanner(closestEditingHost
, rangeToDelete
.StartRef(),
8354 BlockInlineCheck::UseComputedDisplayOutsideStyle
)
8355 .GetStartReasonContent());
8356 // We want to keep looking up. But stop if we are crossing table
8357 // element boundaries, or if we hit the root.
8358 if (HTMLEditUtils::IsAnyTableElement(
8359 backwardScanFromStartResult
.GetContent()) ||
8360 backwardScanFromStartResult
.GetContent() ==
8361 closestBlockAncestorOrInlineEditingHost
||
8362 backwardScanFromStartResult
.GetContent() == closestEditingHost
) {
8365 // Don't cross list element boundary because we don't want to delete list
8366 // element at start position unless it's empty.
8367 if (HTMLEditUtils::IsAnyListElement(
8368 backwardScanFromStartResult
.GetContent()) &&
8369 !HTMLEditUtils::IsEmptyAnyListElement(
8370 *backwardScanFromStartResult
.ElementPtr())) {
8373 // Don't cross flex-item/grid-item boundary to make new content inserted
8375 if (backwardScanFromStartResult
.ContentIsElement() &&
8376 HTMLEditUtils::IsFlexOrGridItem(
8377 *backwardScanFromStartResult
.ElementPtr())) {
8380 rangeToDelete
.SetStart(backwardScanFromStartResult
8381 .PointAtReachedContent
<EditorRawDOMPoint
>());
8383 if (!aLimitersAndCaretData
.NodeIsInLimiters(
8384 rangeToDelete
.StartRef().GetContainer())) {
8385 NS_WARNING("Computed start container was out of selection limiter");
8386 return Err(NS_ERROR_FAILURE
);
8390 // Expand selection endpoint only if we don't pass an invisible `<br>`, or if
8391 // we really needed to pass that `<br>` (i.e., its block is now totally
8394 // Find next visible things after end of selection
8395 EditorDOMPoint atFirstInvisibleBRElement
;
8396 if (rangeToDelete
.EndRef().GetContainer() !=
8397 closestBlockAncestorOrInlineEditingHost
) {
8399 WSRunScanner
wsScannerAtEnd(
8400 closestEditingHost
, rangeToDelete
.EndRef(),
8401 BlockInlineCheck::UseComputedDisplayOutsideStyle
);
8402 const WSScanResult forwardScanFromEndResult
=
8403 wsScannerAtEnd
.ScanInclusiveNextVisibleNodeOrBlockBoundaryFrom(
8404 rangeToDelete
.EndRef());
8405 if (forwardScanFromEndResult
.ReachedBRElement()) {
8406 // XXX In my understanding, this is odd. The end reason may not be
8407 // same as the reached <br> element because the equality is
8408 // guaranteed only when ReachedCurrentBlockBoundary() returns true.
8409 // However, looks like that this code assumes that
8410 // GetEndReasonContent() returns the (or a) <br> element.
8411 NS_ASSERTION(wsScannerAtEnd
.GetEndReasonContent() ==
8412 forwardScanFromEndResult
.BRElementPtr(),
8413 "End reason is not the reached <br> element");
8414 if (HTMLEditUtils::IsVisibleBRElement(
8415 *wsScannerAtEnd
.GetEndReasonContent())) {
8418 if (!atFirstInvisibleBRElement
.IsSet()) {
8419 atFirstInvisibleBRElement
=
8420 rangeToDelete
.EndRef().To
<EditorDOMPoint
>();
8422 rangeToDelete
.SetEnd(
8423 EditorRawDOMPoint::After(*wsScannerAtEnd
.GetEndReasonContent()));
8427 if (forwardScanFromEndResult
.ReachedCurrentBlockBoundary() ||
8428 forwardScanFromEndResult
.ReachedInlineEditingHostBoundary()) {
8429 MOZ_ASSERT(forwardScanFromEndResult
.ContentIsElement());
8430 MOZ_ASSERT(forwardScanFromEndResult
.GetContent() ==
8431 wsScannerAtEnd
.GetEndReasonContent());
8432 // We want to keep looking up. But stop if we are crossing table
8433 // element boundaries, or if we hit the root.
8434 if (HTMLEditUtils::IsAnyTableElement(
8435 forwardScanFromEndResult
.GetContent()) ||
8436 forwardScanFromEndResult
.GetContent() ==
8437 closestBlockAncestorOrInlineEditingHost
) {
8440 // Don't cross flex-item/grid-item boundary to make new content inserted
8442 if (HTMLEditUtils::IsFlexOrGridItem(
8443 *forwardScanFromEndResult
.ElementPtr())) {
8446 rangeToDelete
.SetEnd(
8447 forwardScanFromEndResult
8448 .PointAfterReachedContent
<EditorRawDOMPoint
>());
8455 if (!aLimitersAndCaretData
.NodeIsInLimiters(
8456 rangeToDelete
.EndRef().GetContainer())) {
8457 NS_WARNING("Computed end container was out of selection limiter");
8458 return Err(NS_ERROR_FAILURE
);
8462 // If range boundaries are in list element, and the positions are very
8463 // start/end of first/last list item, we may need to shrink the ranges for
8464 // preventing to remove only all list item elements.
8466 EditorRawDOMRange rangeToDeleteListOrLeaveOneEmptyListItem
=
8467 AutoDeleteRangesHandler::
8468 GetRangeToAvoidDeletingAllListItemsIfSelectingAllOverListElements(
8470 if (rangeToDeleteListOrLeaveOneEmptyListItem
.IsPositioned()) {
8471 rangeToDelete
= std::move(rangeToDeleteListOrLeaveOneEmptyListItem
);
8475 if (atFirstInvisibleBRElement
.IsInContentNode()) {
8476 // Find block node containing invisible `<br>` element.
8477 if (const RefPtr
<const Element
> editableBlockContainingBRElement
=
8478 HTMLEditUtils::GetInclusiveAncestorElement(
8479 *atFirstInvisibleBRElement
.ContainerAs
<nsIContent
>(),
8480 HTMLEditUtils::ClosestEditableBlockElement
,
8481 BlockInlineCheck::UseComputedDisplayOutsideStyle
)) {
8482 if (rangeToDelete
.Contains(
8483 EditorRawDOMPoint(editableBlockContainingBRElement
))) {
8484 return rangeToDelete
;
8486 // Otherwise, the new range should end at the invisible `<br>`.
8487 if (!aLimitersAndCaretData
.NodeIsInLimiters(
8488 atFirstInvisibleBRElement
.GetContainer())) {
8490 "Computed end container (`<br>` element) was out of selection "
8492 return Err(NS_ERROR_FAILURE
);
8494 rangeToDelete
.SetEnd(atFirstInvisibleBRElement
);
8498 return rangeToDelete
;
8502 EditorRawDOMRange
HTMLEditor::AutoDeleteRangesHandler::
8503 GetRangeToAvoidDeletingAllListItemsIfSelectingAllOverListElements(
8504 const EditorRawDOMRange
& aRangeToDelete
) {
8505 MOZ_ASSERT(aRangeToDelete
.IsPositionedAndValid());
8507 auto GetDeepestEditableStartPointOfList
= [](Element
& aListElement
) {
8508 Element
* const firstListItemElement
=
8509 HTMLEditUtils::GetFirstListItemElement(aListElement
);
8510 if (MOZ_UNLIKELY(!firstListItemElement
)) {
8511 return EditorRawDOMPoint();
8513 if (MOZ_UNLIKELY(!EditorUtils::IsEditableContent(*firstListItemElement
,
8514 EditorType::HTML
))) {
8515 return EditorRawDOMPoint(firstListItemElement
);
8517 return HTMLEditUtils::GetDeepestEditableStartPointOf
<EditorRawDOMPoint
>(
8518 *firstListItemElement
);
8521 auto GetDeepestEditableEndPointOfList
= [](Element
& aListElement
) {
8522 Element
* const lastListItemElement
=
8523 HTMLEditUtils::GetLastListItemElement(aListElement
);
8524 if (MOZ_UNLIKELY(!lastListItemElement
)) {
8525 return EditorRawDOMPoint();
8527 if (MOZ_UNLIKELY(!EditorUtils::IsEditableContent(*lastListItemElement
,
8528 EditorType::HTML
))) {
8529 return EditorRawDOMPoint::After(*lastListItemElement
);
8531 return HTMLEditUtils::GetDeepestEditableEndPointOf
<EditorRawDOMPoint
>(
8532 *lastListItemElement
);
8535 Element
* const startListElement
=
8536 aRangeToDelete
.StartRef().IsInContentNode()
8537 ? HTMLEditUtils::GetClosestInclusiveAncestorAnyListElement(
8538 *aRangeToDelete
.StartRef().ContainerAs
<nsIContent
>())
8540 Element
* const endListElement
=
8541 aRangeToDelete
.EndRef().IsInContentNode()
8542 ? HTMLEditUtils::GetClosestInclusiveAncestorAnyListElement(
8543 *aRangeToDelete
.EndRef().ContainerAs
<nsIContent
>())
8545 if (!startListElement
&& !endListElement
) {
8546 return EditorRawDOMRange();
8549 // FIXME: If there are invalid children, we cannot handle first/last list item
8550 // elements properly. In that case, we should treat list elements and list
8551 // item elements as normal block elements.
8552 if (startListElement
&&
8553 NS_WARN_IF(!HTMLEditUtils::IsValidListElement(
8554 *startListElement
, HTMLEditUtils::TreatSubListElementAs::Valid
))) {
8555 return EditorRawDOMRange();
8557 if (endListElement
&& startListElement
!= endListElement
&&
8558 NS_WARN_IF(!HTMLEditUtils::IsValidListElement(
8559 *endListElement
, HTMLEditUtils::TreatSubListElementAs::Valid
))) {
8560 return EditorRawDOMRange();
8563 const bool startListElementIsEmpty
=
8565 HTMLEditUtils::IsEmptyAnyListElement(*startListElement
);
8566 const bool endListElementIsEmpty
=
8567 startListElement
== endListElement
8568 ? startListElementIsEmpty
8570 HTMLEditUtils::IsEmptyAnyListElement(*endListElement
);
8571 // If both list elements are empty, we should not shrink the range since
8572 // we want to delete the list.
8573 if (startListElementIsEmpty
&& endListElementIsEmpty
) {
8574 return EditorRawDOMRange();
8577 // There may be invisible white-spaces and there are elements in the
8578 // list items. Therefore, we need to compare the deepest positions
8579 // and range boundaries.
8580 EditorRawDOMPoint deepestStartPointOfStartList
=
8581 startListElement
? GetDeepestEditableStartPointOfList(*startListElement
)
8582 : EditorRawDOMPoint();
8583 EditorRawDOMPoint deepestEndPointOfEndList
=
8584 endListElement
? GetDeepestEditableEndPointOfList(*endListElement
)
8585 : EditorRawDOMPoint();
8586 if (MOZ_UNLIKELY(!deepestStartPointOfStartList
.IsSet() &&
8587 !deepestEndPointOfEndList
.IsSet())) {
8588 // FIXME: This does not work well if there is non-list-item contents in the
8589 // list elements. Perhaps, for fixing this invalid cases, we need to wrap
8590 // the content into new list item like Chrome.
8591 return EditorRawDOMRange();
8594 // We don't want to shrink the range into empty sublist.
8595 if (deepestStartPointOfStartList
.IsSet()) {
8596 for (nsIContent
* const maybeList
:
8597 deepestStartPointOfStartList
.GetContainer()
8598 ->InclusiveAncestorsOfType
<nsIContent
>()) {
8599 if (aRangeToDelete
.StartRef().GetContainer() == maybeList
) {
8602 if (HTMLEditUtils::IsAnyListElement(maybeList
) &&
8603 HTMLEditUtils::IsEmptyAnyListElement(*maybeList
->AsElement())) {
8604 deepestStartPointOfStartList
.Set(maybeList
);
8608 if (deepestEndPointOfEndList
.IsSet()) {
8609 for (nsIContent
* const maybeList
:
8610 deepestEndPointOfEndList
.GetContainer()
8611 ->InclusiveAncestorsOfType
<nsIContent
>()) {
8612 if (aRangeToDelete
.EndRef().GetContainer() == maybeList
) {
8615 if (HTMLEditUtils::IsAnyListElement(maybeList
) &&
8616 HTMLEditUtils::IsEmptyAnyListElement(*maybeList
->AsElement())) {
8617 deepestEndPointOfEndList
.SetAfter(maybeList
);
8622 const EditorRawDOMPoint deepestEndPointOfStartList
=
8623 startListElement
? GetDeepestEditableEndPointOfList(*startListElement
)
8624 : EditorRawDOMPoint();
8625 MOZ_ASSERT_IF(deepestStartPointOfStartList
.IsSet(),
8626 deepestEndPointOfStartList
.IsSet());
8627 MOZ_ASSERT_IF(!deepestStartPointOfStartList
.IsSet(),
8628 !deepestEndPointOfStartList
.IsSet());
8630 const bool rangeStartsFromBeginningOfStartList
=
8631 deepestStartPointOfStartList
.IsSet() &&
8632 aRangeToDelete
.StartRef().EqualsOrIsBefore(deepestStartPointOfStartList
);
8633 const bool rangeEndsByEndingOfStartListOrLater
=
8634 !deepestEndPointOfStartList
.IsSet() ||
8635 deepestEndPointOfStartList
.EqualsOrIsBefore(aRangeToDelete
.EndRef());
8636 const bool rangeEndsByEndingOfEndList
=
8637 deepestEndPointOfEndList
.IsSet() &&
8638 deepestEndPointOfEndList
.EqualsOrIsBefore(aRangeToDelete
.EndRef());
8640 EditorRawDOMRange newRangeToDelete
;
8641 // If all over the list element at start boundary is selected, we should
8642 // shrink the range to start from the first list item to avoid to delete
8644 if (!startListElementIsEmpty
&& rangeStartsFromBeginningOfStartList
&&
8645 rangeEndsByEndingOfStartListOrLater
) {
8646 newRangeToDelete
.SetStart(EditorRawDOMPoint(
8647 deepestStartPointOfStartList
.ContainerAs
<nsIContent
>(), 0u));
8649 // If all over the list element at end boundary is selected, and...
8650 if (!endListElementIsEmpty
&& rangeEndsByEndingOfEndList
) {
8651 // If the range starts before the range at end boundary of the range,
8652 // we want to delete the list completely, thus, we should extend the
8653 // range to contain the list element.
8654 if (aRangeToDelete
.StartRef().IsBefore(
8655 EditorRawDOMPoint(endListElement
, 0u))) {
8656 newRangeToDelete
.SetEnd(EditorRawDOMPoint::After(*endListElement
));
8657 MOZ_ASSERT_IF(newRangeToDelete
.StartRef().IsSet(),
8658 newRangeToDelete
.IsPositionedAndValid());
8660 // Otherwise, if the range starts in the end list element, we shouldn't
8661 // delete the list. Therefore, we should shrink the range to end by end
8662 // of the last list item element to avoid to delete all list items.
8664 newRangeToDelete
.SetEnd(EditorRawDOMPoint::AtEndOf(
8665 *deepestEndPointOfEndList
.ContainerAs
<nsIContent
>()));
8666 MOZ_ASSERT_IF(newRangeToDelete
.StartRef().IsSet(),
8667 newRangeToDelete
.IsPositionedAndValid());
8671 if (!newRangeToDelete
.StartRef().IsSet() &&
8672 !newRangeToDelete
.EndRef().IsSet()) {
8673 return EditorRawDOMRange();
8676 if (!newRangeToDelete
.StartRef().IsSet()) {
8677 newRangeToDelete
.SetStart(aRangeToDelete
.StartRef());
8678 MOZ_ASSERT(newRangeToDelete
.IsPositionedAndValid());
8680 if (!newRangeToDelete
.EndRef().IsSet()) {
8681 newRangeToDelete
.SetEnd(aRangeToDelete
.EndRef());
8682 MOZ_ASSERT(newRangeToDelete
.IsPositionedAndValid());
8685 return newRangeToDelete
;
8688 } // namespace mozilla