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"
13 #include "AutoRangeArray.h"
14 #include "CSSEditUtils.h"
15 #include "EditAction.h"
16 #include "EditorDOMPoint.h"
17 #include "EditorUtils.h"
18 #include "HTMLEditHelpers.h"
19 #include "HTMLEditorInlines.h"
20 #include "HTMLEditUtils.h"
21 #include "WSRunObject.h"
23 #include "ErrorList.h"
24 #include "js/ErrorReport.h"
25 #include "mozilla/Assertions.h"
26 #include "mozilla/CheckedInt.h"
27 #include "mozilla/ComputedStyle.h" // for ComputedStyle
28 #include "mozilla/ContentIterator.h"
29 #include "mozilla/EditorDOMPoint.h"
30 #include "mozilla/EditorForwards.h"
31 #include "mozilla/InternalMutationEvent.h"
32 #include "mozilla/Logging.h"
33 #include "mozilla/Maybe.h"
34 #include "mozilla/OwningNonNull.h"
35 #include "mozilla/SelectionState.h"
36 #include "mozilla/StaticPrefs_editor.h" // for StaticPrefs::editor_*
37 #include "mozilla/Unused.h"
38 #include "mozilla/dom/AncestorIterator.h"
39 #include "mozilla/dom/Element.h"
40 #include "mozilla/dom/HTMLBRElement.h"
41 #include "mozilla/dom/Selection.h"
42 #include "mozilla/mozalloc.h"
43 #include "nsAString.h"
45 #include "nsComputedDOMStyle.h" // for nsComputedDOMStyle
46 #include "nsContentUtils.h"
49 #include "nsFrameSelection.h"
50 #include "nsGkAtoms.h"
51 #include "nsIContent.h"
55 #include "nsStringFwd.h"
56 #include "nsStyleConsts.h" // for StyleWhiteSpace
59 // NOTE: This file was split from:
60 // https://searchfox.org/mozilla-central/rev/c409dd9235c133ab41eba635f906aa16e050c197/editor/libeditor/HTMLEditSubActionHandler.cpp
65 using EmptyCheckOption
= HTMLEditUtils::EmptyCheckOption
;
66 using InvisibleWhiteSpaces
= HTMLEditUtils::InvisibleWhiteSpaces
;
67 using LeafNodeType
= HTMLEditUtils::LeafNodeType
;
68 using ScanLineBreak
= HTMLEditUtils::ScanLineBreak
;
69 using TableBoundary
= HTMLEditUtils::TableBoundary
;
70 using WalkTreeOption
= HTMLEditUtils::WalkTreeOption
;
72 static LazyLogModule
gOneLineMoverLog("AutoMoveOneLineHandler");
74 template Result
<CaretPoint
, nsresult
>
75 HTMLEditor::DeleteTextAndTextNodesWithTransaction(
76 const EditorDOMPoint
& aStartPoint
, const EditorDOMPoint
& aEndPoint
,
77 TreatEmptyTextNodes aTreatEmptyTextNodes
);
78 template Result
<CaretPoint
, nsresult
>
79 HTMLEditor::DeleteTextAndTextNodesWithTransaction(
80 const EditorDOMPointInText
& aStartPoint
,
81 const EditorDOMPointInText
& aEndPoint
,
82 TreatEmptyTextNodes aTreatEmptyTextNodes
);
84 /*****************************************************************************
85 * AutoSetTemporaryAncestorLimiter
86 ****************************************************************************/
88 class MOZ_RAII AutoSetTemporaryAncestorLimiter final
{
90 AutoSetTemporaryAncestorLimiter(const HTMLEditor
& aHTMLEditor
,
91 Selection
& aSelection
,
92 nsINode
& aStartPointNode
,
93 AutoRangeArray
* aRanges
= nullptr) {
94 MOZ_ASSERT(aSelection
.GetType() == SelectionType::eNormal
);
96 if (aSelection
.GetAncestorLimiter()) {
100 Element
* selectionRootElement
=
101 aHTMLEditor
.FindSelectionRoot(aStartPointNode
);
102 if (!selectionRootElement
) {
105 aHTMLEditor
.InitializeSelectionAncestorLimit(*selectionRootElement
);
106 mSelection
= &aSelection
;
107 // Setting ancestor limiter may change ranges which were outer of
108 // the new limiter. Therefore, we need to reinitialize aRanges.
110 aRanges
->Initialize(aSelection
);
114 ~AutoSetTemporaryAncestorLimiter() {
116 mSelection
->SetAncestorLimiter(nullptr);
121 RefPtr
<Selection
> mSelection
;
124 /*****************************************************************************
125 * AutoDeleteRangesHandler
126 ****************************************************************************/
128 class MOZ_STACK_CLASS
HTMLEditor::AutoDeleteRangesHandler final
{
130 explicit AutoDeleteRangesHandler(
131 const AutoDeleteRangesHandler
* aParent
= nullptr)
133 mOriginalDirectionAndAmount(nsIEditor::eNone
),
134 mOriginalStripWrappers(nsIEditor::eNoStrip
) {}
137 * ComputeRangesToDelete() computes actual deletion ranges.
139 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT nsresult
ComputeRangesToDelete(
140 const HTMLEditor
& aHTMLEditor
, nsIEditor::EDirection aDirectionAndAmount
,
141 AutoRangeArray
& aRangesToDelete
, const Element
& aEditingHost
);
144 * Deletes content in or around aRangesToDelete.
145 * NOTE: This method creates SelectionBatcher. Therefore, each caller
146 * needs to check if the editor is still available even if this returns
149 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT Result
<EditActionResult
, nsresult
> Run(
150 HTMLEditor
& aHTMLEditor
, nsIEditor::EDirection aDirectionAndAmount
,
151 nsIEditor::EStripWrappers aStripWrappers
, AutoRangeArray
& aRangesToDelete
,
152 const Element
& aEditingHost
);
155 [[nodiscard
]] bool IsHandlingRecursively() const {
156 return mParent
!= nullptr;
159 [[nodiscard
]] bool CanFallbackToDeleteRangeWithTransaction(
160 const nsRange
& aRangeToDelete
) const {
161 return !IsHandlingRecursively() &&
162 (!aRangeToDelete
.Collapsed() ||
163 EditorBase::HowToHandleCollapsedRangeFor(
164 mOriginalDirectionAndAmount
) !=
165 EditorBase::HowToHandleCollapsedRange::Ignore
);
168 [[nodiscard
]] bool CanFallbackToDeleteRangesWithTransaction(
169 const AutoRangeArray
& aRangesToDelete
) const {
170 return !IsHandlingRecursively() && !aRangesToDelete
.Ranges().IsEmpty() &&
171 (!aRangesToDelete
.IsCollapsed() ||
172 EditorBase::HowToHandleCollapsedRangeFor(
173 mOriginalDirectionAndAmount
) !=
174 EditorBase::HowToHandleCollapsedRange::Ignore
);
178 * HandleDeleteAroundCollapsedRanges() handles deletion with collapsed
179 * ranges. Callers must guarantee that this is called only when
180 * aRangesToDelete.IsCollapsed() returns true.
182 * @param aDirectionAndAmount Direction of the deletion.
183 * @param aStripWrappers Must be eStrip or eNoStrip.
184 * @param aRangesToDelete Ranges to delete. This `IsCollapsed()` must
186 * @param aWSRunScannerAtCaret Scanner instance which scanned from
188 * @param aScanFromCaretPointResult Scan result of aWSRunScannerAtCaret
189 * toward aDirectionAndAmount.
190 * @param aEditingHost The editing host.
192 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT Result
<EditActionResult
, nsresult
>
193 HandleDeleteAroundCollapsedRanges(
194 HTMLEditor
& aHTMLEditor
, nsIEditor::EDirection aDirectionAndAmount
,
195 nsIEditor::EStripWrappers aStripWrappers
, AutoRangeArray
& aRangesToDelete
,
196 const WSRunScanner
& aWSRunScannerAtCaret
,
197 const WSScanResult
& aScanFromCaretPointResult
,
198 const Element
& aEditingHost
);
199 nsresult
ComputeRangesToDeleteAroundCollapsedRanges(
200 const HTMLEditor
& aHTMLEditor
, nsIEditor::EDirection aDirectionAndAmount
,
201 AutoRangeArray
& aRangesToDelete
, const WSRunScanner
& aWSRunScannerAtCaret
,
202 const WSScanResult
& aScanFromCaretPointResult
,
203 const Element
& aEditingHost
) const;
206 * HandleDeleteNonCollapsedRanges() handles deletion with non-collapsed
207 * ranges. Callers must guarantee that this is called only when
208 * aRangesToDelete.IsCollapsed() returns false.
210 * @param aDirectionAndAmount Direction of the deletion.
211 * @param aStripWrappers Must be eStrip or eNoStrip.
212 * @param aRangesToDelete The ranges to delete.
213 * @param aSelectionWasCollapsed If the caller extended `Selection`
214 * from collapsed, set this to `Yes`.
215 * Otherwise, i.e., `Selection` is not
216 * collapsed from the beginning, set
218 * @param aEditingHost The editing host.
220 enum class SelectionWasCollapsed
{ Yes
, No
};
221 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT Result
<EditActionResult
, nsresult
>
222 HandleDeleteNonCollapsedRanges(HTMLEditor
& aHTMLEditor
,
223 nsIEditor::EDirection aDirectionAndAmount
,
224 nsIEditor::EStripWrappers aStripWrappers
,
225 AutoRangeArray
& aRangesToDelete
,
226 SelectionWasCollapsed aSelectionWasCollapsed
,
227 const Element
& aEditingHost
);
228 nsresult
ComputeRangesToDeleteNonCollapsedRanges(
229 const HTMLEditor
& aHTMLEditor
, nsIEditor::EDirection aDirectionAndAmount
,
230 AutoRangeArray
& aRangesToDelete
,
231 SelectionWasCollapsed aSelectionWasCollapsed
,
232 const Element
& aEditingHost
) const;
235 * Handle deletion of collapsed ranges in a text node.
237 * @param aDirectionAndAmount Must be eNext or ePrevious.
238 * @param aCaretPosition The position where caret is. This container
239 * must be a text node.
240 * @param aEditingHost The editing host.
242 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT Result
<CaretPoint
, nsresult
>
243 HandleDeleteTextAroundCollapsedRanges(
244 HTMLEditor
& aHTMLEditor
, nsIEditor::EDirection aDirectionAndAmount
,
245 AutoRangeArray
& aRangesToDelete
, const Element
& aEditingHost
);
246 nsresult
ComputeRangesToDeleteTextAroundCollapsedRanges(
247 nsIEditor::EDirection aDirectionAndAmount
,
248 AutoRangeArray
& aRangesToDelete
, const Element
& aEditingHost
) const;
251 * Handles deletion of collapsed selection at white-spaces in a text node.
253 * @param aDirectionAndAmount Direction of the deletion.
254 * @param aPointToDelete The point to delete. I.e., typically, caret
256 * @param aEditingHost The editing host.
258 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT Result
<CaretPoint
, nsresult
>
259 HandleDeleteCollapsedSelectionAtWhiteSpaces(
260 HTMLEditor
& aHTMLEditor
, nsIEditor::EDirection aDirectionAndAmount
,
261 const EditorDOMPoint
& aPointToDelete
, const Element
& aEditingHost
);
264 * Handle deletion of collapsed selection in a text node.
266 * @param aDirectionAndAmount Direction of the deletion.
267 * @param aRangesToDelete Computed selection ranges to delete.
268 * @param aPointAtDeletingChar The visible char position which you want to
270 * @param aEditingHost The editing host.
272 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT Result
<CaretPoint
, nsresult
>
273 HandleDeleteCollapsedSelectionAtVisibleChar(
274 HTMLEditor
& aHTMLEditor
, nsIEditor::EDirection aDirectionAndAmount
,
275 AutoRangeArray
& aRangesToDelete
,
276 const EditorDOMPoint
& aPointAtDeletingChar
, const Element
& aEditingHost
);
279 * Handle deletion of atomic elements like <br>, <hr>, <img>, <input>, etc and
280 * data nodes except text node (e.g., comment node). Note that don't call this
281 * directly with `<hr>` element.
283 * @param aAtomicContent The atomic content to be deleted.
284 * @param aCaretPoint The caret point (i.e., selection start or
286 * @param aWSRunScannerAtCaret WSRunScanner instance which was initialized
287 * with the caret point.
288 * @param aEditingHost The editing host.
290 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT Result
<CaretPoint
, nsresult
>
291 HandleDeleteAtomicContent(HTMLEditor
& aHTMLEditor
, nsIContent
& aAtomicContent
,
292 const EditorDOMPoint
& aCaretPoint
,
293 const WSRunScanner
& aWSRunScannerAtCaret
,
294 const Element
& aEditingHost
);
295 nsresult
ComputeRangesToDeleteAtomicContent(
296 Element
* aEditingHost
, const nsIContent
& aAtomicContent
,
297 AutoRangeArray
& aRangesToDelete
) const;
300 * GetAtomicContnetToDelete() returns better content that is deletion of
301 * atomic element. If aScanFromCaretPointResult is special, since this
302 * point may not be editable, we look for better point to remove atomic
305 * @param aDirectionAndAmount Direction of the deletion.
306 * @param aWSRunScannerAtCaret WSRunScanner instance which was
307 * initialized with the caret point.
308 * @param aScanFromCaretPointResult Scan result of aWSRunScannerAtCaret
309 * toward aDirectionAndAmount.
311 static nsIContent
* GetAtomicContentToDelete(
312 nsIEditor::EDirection aDirectionAndAmount
,
313 const WSRunScanner
& aWSRunScannerAtCaret
,
314 const WSScanResult
& aScanFromCaretPointResult
) MOZ_NONNULL_RETURN
;
317 * HandleDeleteAtOtherBlockBoundary() handles deletion at other block boundary
318 * (i.e., immediately before or after a block). If this does not join blocks,
319 * `Run()` may be called recursively with creating another instance.
321 * @param aDirectionAndAmount Direction of the deletion.
322 * @param aStripWrappers Must be eStrip or eNoStrip.
323 * @param aOtherBlockElement The block element which follows the caret or
324 * is followed by caret.
325 * @param aCaretPoint The caret point (i.e., selection start or
327 * @param aWSRunScannerAtCaret WSRunScanner instance which was initialized
328 * with the caret point.
329 * @param aRangesToDelete Ranges to delete of the caller. This should
330 * be collapsed and the point should match with
332 * @param aEditingHost The editing host.
334 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT Result
<EditActionResult
, nsresult
>
335 HandleDeleteAtOtherBlockBoundary(
336 HTMLEditor
& aHTMLEditor
, nsIEditor::EDirection aDirectionAndAmount
,
337 nsIEditor::EStripWrappers aStripWrappers
, Element
& aOtherBlockElement
,
338 const EditorDOMPoint
& aCaretPoint
, WSRunScanner
& aWSRunScannerAtCaret
,
339 AutoRangeArray
& aRangesToDelete
, const Element
& aEditingHost
);
342 * ExtendOrShrinkRangeToDelete() extends aRangeToDelete if there are
343 * an invisible <br> element and/or some parent empty elements.
345 * @param aFrameSelection If the caller wants range in selection limiter,
346 * set this to non-nullptr which knows the limiter.
347 * @param aRangeToDelete The range to be extended for deletion. This
348 * must not be collapsed, must be positioned.
350 template <typename EditorDOMRangeType
>
351 Result
<EditorRawDOMRange
, nsresult
> ExtendOrShrinkRangeToDelete(
352 const HTMLEditor
& aHTMLEditor
, const nsFrameSelection
* aFrameSelection
,
353 const EditorDOMRangeType
& aRangeToDelete
) const;
356 * A helper method for ExtendOrShrinkRangeToDelete(). This returns shrunken
357 * range if aRangeToDelete selects all over list elements which have some list
358 * item elements to avoid to delete all list items from the list element.
360 MOZ_NEVER_INLINE_DEBUG
static EditorRawDOMRange
361 GetRangeToAvoidDeletingAllListItemsIfSelectingAllOverListElements(
362 const EditorRawDOMRange
& aRangeToDelete
);
365 * DeleteUnnecessaryNodes() removes unnecessary nodes around aRange.
366 * Note that aRange is tracked with AutoTrackDOMRange.
368 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT nsresult
369 DeleteUnnecessaryNodes(HTMLEditor
& aHTMLEditor
, EditorDOMRange
& aRange
);
372 * DeleteUnnecessaryNodesAndCollapseSelection() calls DeleteUnnecessaryNodes()
373 * and then, collapse selection at tracked aSelectionStartPoint or
374 * aSelectionEndPoint (depending on aDirectionAndAmount).
376 * @param aDirectionAndAmount Direction of the deletion.
377 * If nsIEditor::ePrevious, selection
378 * will be collapsed to aSelectionEndPoint.
379 * Otherwise, selection will be collapsed
380 * to aSelectionStartPoint.
381 * @param aSelectionStartPoint First selection range start after
382 * computing the deleting range.
383 * @param aSelectionEndPoint First selection range end after
384 * computing the deleting range.
386 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT nsresult
387 DeleteUnnecessaryNodesAndCollapseSelection(
388 HTMLEditor
& aHTMLEditor
, nsIEditor::EDirection aDirectionAndAmount
,
389 const EditorDOMPoint
& aSelectionStartPoint
,
390 const EditorDOMPoint
& aSelectionEndPoint
);
393 * If aContent is a text node that contains only collapsed white-space or
394 * empty and editable.
396 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT nsresult
397 DeleteNodeIfInvisibleAndEditableTextNode(HTMLEditor
& aHTMLEditor
,
398 nsIContent
& aContent
);
401 * DeleteParentBlocksIfEmpty() removes parent block elements if they
402 * don't have visible contents. Note that due performance issue of
403 * WhiteSpaceVisibilityKeeper, this call may be expensive. And also note that
404 * this removes a empty block with a transaction. So, please make sure that
405 * you've already created `AutoPlaceholderBatch`.
407 * @param aPoint The point whether this method climbing up the DOM
408 * tree to remove empty parent blocks.
409 * @return NS_OK if one or more empty block parents are deleted.
410 * NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND if the point is
411 * not in empty block.
412 * Or NS_ERROR_* if something unexpected occurs.
414 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT nsresult
415 DeleteParentBlocksWithTransactionIfEmpty(HTMLEditor
& aHTMLEditor
,
416 const EditorDOMPoint
& aPoint
);
418 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT Result
<CaretPoint
, nsresult
>
419 FallbackToDeleteRangeWithTransaction(HTMLEditor
& aHTMLEditor
,
420 nsRange
& aRangeToDelete
) const {
421 MOZ_ASSERT(aHTMLEditor
.IsEditActionDataAvailable());
422 MOZ_ASSERT(CanFallbackToDeleteRangeWithTransaction(aRangeToDelete
));
423 Result
<CaretPoint
, nsresult
> caretPointOrError
=
424 aHTMLEditor
.DeleteRangeWithTransaction(mOriginalDirectionAndAmount
,
425 mOriginalStripWrappers
,
427 NS_WARNING_ASSERTION(caretPointOrError
.isOk(),
428 "EditorBase::DeleteRangeWithTransaction() failed");
429 return caretPointOrError
;
432 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT Result
<CaretPoint
, nsresult
>
433 FallbackToDeleteRangesWithTransaction(HTMLEditor
& aHTMLEditor
,
434 AutoRangeArray
& aRangesToDelete
) const {
435 MOZ_ASSERT(aHTMLEditor
.IsEditActionDataAvailable());
436 MOZ_ASSERT(CanFallbackToDeleteRangesWithTransaction(aRangesToDelete
));
437 Result
<CaretPoint
, nsresult
> caretPointOrError
=
438 aHTMLEditor
.DeleteRangesWithTransaction(mOriginalDirectionAndAmount
,
439 mOriginalStripWrappers
,
441 NS_WARNING_ASSERTION(caretPointOrError
.isOk(),
442 "EditorBase::DeleteRangesWithTransaction() failed");
443 return caretPointOrError
;
447 * Compute target range(s) which will be called by
448 * `EditorBase::DeleteRangeWithTransaction()` or
449 * `EditorBase::DeleteRangesWithTransaction()`.
450 * TODO: We should not use it for consistency with each deletion handler
451 * in this and nested classes.
453 nsresult
ComputeRangeToDeleteRangeWithTransaction(
454 const HTMLEditor
& aHTMLEditor
, nsIEditor::EDirection aDirectionAndAmount
,
455 nsRange
& aRange
, const Element
& aEditingHost
) const;
456 nsresult
ComputeRangesToDeleteRangesWithTransaction(
457 const HTMLEditor
& aHTMLEditor
, nsIEditor::EDirection aDirectionAndAmount
,
458 AutoRangeArray
& aRangesToDelete
, const Element
& aEditingHost
) const {
459 MOZ_ASSERT(!aRangesToDelete
.Ranges().IsEmpty());
460 const EditorBase::HowToHandleCollapsedRange howToHandleCollapsedRange
=
461 EditorBase::HowToHandleCollapsedRangeFor(aDirectionAndAmount
);
462 if (NS_WARN_IF(aRangesToDelete
.IsCollapsed() &&
463 howToHandleCollapsedRange
==
464 EditorBase::HowToHandleCollapsedRange::Ignore
)) {
465 return NS_ERROR_FAILURE
;
468 for (const OwningNonNull
<nsRange
>& range
: aRangesToDelete
.Ranges()) {
469 if (range
->Collapsed()) {
472 nsresult rv
= ComputeRangeToDeleteRangeWithTransaction(
473 aHTMLEditor
, aDirectionAndAmount
, range
, aEditingHost
);
476 "AutoDeleteRangesHandler::ComputeRangeToDeleteRangeWithTransaction("
484 nsresult
FallbackToComputeRangeToDeleteRangeWithTransaction(
485 const HTMLEditor
& aHTMLEditor
, nsRange
& aRangeToDelete
,
486 const Element
& aEditingHost
) const {
487 MOZ_ASSERT(aHTMLEditor
.IsEditActionDataAvailable());
488 MOZ_ASSERT(CanFallbackToDeleteRangeWithTransaction(aRangeToDelete
));
489 nsresult rv
= ComputeRangeToDeleteRangeWithTransaction(
490 aHTMLEditor
, mOriginalDirectionAndAmount
, aRangeToDelete
, aEditingHost
);
491 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
492 "AutoDeleteRangesHandler::"
493 "ComputeRangeToDeleteRangeWithTransaction() failed");
496 nsresult
FallbackToComputeRangesToDeleteRangesWithTransaction(
497 const HTMLEditor
& aHTMLEditor
, AutoRangeArray
& aRangesToDelete
,
498 const Element
& aEditingHost
) const {
499 MOZ_ASSERT(aHTMLEditor
.IsEditActionDataAvailable());
500 MOZ_ASSERT(CanFallbackToDeleteRangesWithTransaction(aRangesToDelete
));
501 nsresult rv
= ComputeRangesToDeleteRangesWithTransaction(
502 aHTMLEditor
, mOriginalDirectionAndAmount
, aRangesToDelete
,
504 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
505 "AutoDeleteRangesHandler::"
506 "ComputeRangesToDeleteRangesWithTransaction() failed");
510 class MOZ_STACK_CLASS AutoBlockElementsJoiner final
{
512 AutoBlockElementsJoiner() = delete;
513 explicit AutoBlockElementsJoiner(
514 AutoDeleteRangesHandler
& aDeleteRangesHandler
)
515 : mDeleteRangesHandler(&aDeleteRangesHandler
),
516 mDeleteRangesHandlerConst(aDeleteRangesHandler
) {}
517 explicit AutoBlockElementsJoiner(
518 const AutoDeleteRangesHandler
& aDeleteRangesHandler
)
519 : mDeleteRangesHandler(nullptr),
520 mDeleteRangesHandlerConst(aDeleteRangesHandler
) {}
523 * PrepareToDeleteAtCurrentBlockBoundary() considers left content and right
524 * content which are joined for handling deletion at current block boundary
525 * (i.e., at start or end of the current block).
527 * @param aHTMLEditor The HTML editor.
528 * @param aDirectionAndAmount Direction of the deletion.
529 * @param aCurrentBlockElement The current block element.
530 * @param aCaretPoint The caret point (i.e., selection start
532 * @return true if can continue to handle the
535 bool PrepareToDeleteAtCurrentBlockBoundary(
536 const HTMLEditor
& aHTMLEditor
,
537 nsIEditor::EDirection aDirectionAndAmount
,
538 Element
& aCurrentBlockElement
, const EditorDOMPoint
& aCaretPoint
);
541 * PrepareToDeleteAtOtherBlockBoundary() considers left content and right
542 * content which are joined for handling deletion at other block boundary
543 * (i.e., immediately before or after a block).
545 * @param aHTMLEditor The HTML editor.
546 * @param aDirectionAndAmount Direction of the deletion.
547 * @param aOtherBlockElement The block element which follows the
548 * caret or is followed by caret.
549 * @param aCaretPoint The caret point (i.e., selection start
551 * @param aWSRunScannerAtCaret WSRunScanner instance which was
552 * initialized with the caret point.
553 * @return true if can continue to handle the
556 bool PrepareToDeleteAtOtherBlockBoundary(
557 const HTMLEditor
& aHTMLEditor
,
558 nsIEditor::EDirection aDirectionAndAmount
, Element
& aOtherBlockElement
,
559 const EditorDOMPoint
& aCaretPoint
,
560 const WSRunScanner
& aWSRunScannerAtCaret
);
563 * PrepareToDeleteNonCollapsedRange() considers left block element and
564 * right block element which are inclusive ancestor block element of
565 * start and end container of aRangeToDelete
567 * @param aHTMLEditor The HTML editor.
568 * @param aRangeToDelete The range to delete. Must not be
570 * @return true if can continue to handle the
573 bool PrepareToDeleteNonCollapsedRange(const HTMLEditor
& aHTMLEditor
,
574 const nsRange
& aRangeToDelete
);
577 * Run() executes the joining.
579 * @param aHTMLEditor The HTML editor.
580 * @param aDirectionAndAmount Direction of the deletion.
581 * @param aStripWrappers Must be eStrip or eNoStrip.
582 * @param aCaretPoint The caret point (i.e., selection start
584 * @param aRangeToDelete The range to delete. This should be
585 * collapsed and match with aCaretPoint.
587 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT Result
<EditActionResult
, nsresult
> Run(
588 HTMLEditor
& aHTMLEditor
, nsIEditor::EDirection aDirectionAndAmount
,
589 nsIEditor::EStripWrappers aStripWrappers
,
590 const EditorDOMPoint
& aCaretPoint
, nsRange
& aRangeToDelete
,
591 const Element
& aEditingHost
) {
593 case Mode::JoinCurrentBlock
: {
594 Result
<EditActionResult
, nsresult
> result
=
595 HandleDeleteAtCurrentBlockBoundary(
596 aHTMLEditor
, aDirectionAndAmount
, aCaretPoint
, aEditingHost
);
597 NS_WARNING_ASSERTION(result
.isOk(),
598 "AutoBlockElementsJoiner::"
599 "HandleDeleteAtCurrentBlockBoundary() failed");
602 case Mode::JoinOtherBlock
: {
603 Result
<EditActionResult
, nsresult
> result
=
604 HandleDeleteAtOtherBlockBoundary(aHTMLEditor
, aDirectionAndAmount
,
605 aStripWrappers
, aCaretPoint
,
606 aRangeToDelete
, aEditingHost
);
607 NS_WARNING_ASSERTION(result
.isOk(),
608 "AutoBlockElementsJoiner::"
609 "HandleDeleteAtOtherBlockBoundary() failed");
612 case Mode::DeleteBRElement
: {
613 Result
<EditActionResult
, nsresult
> result
=
614 DeleteBRElement(aHTMLEditor
, aDirectionAndAmount
, aEditingHost
);
615 NS_WARNING_ASSERTION(
617 "AutoBlockElementsJoiner::DeleteBRElement() failed");
620 case Mode::JoinBlocksInSameParent
:
621 case Mode::DeleteContentInRange
:
622 case Mode::DeleteNonCollapsedRange
:
623 MOZ_ASSERT_UNREACHABLE(
624 "This mode should be handled in the other Run()");
625 return Err(NS_ERROR_UNEXPECTED
);
626 case Mode::NotInitialized
:
627 return EditActionResult::IgnoredResult();
629 return Err(NS_ERROR_NOT_INITIALIZED
);
632 nsresult
ComputeRangeToDelete(const HTMLEditor
& aHTMLEditor
,
633 nsIEditor::EDirection aDirectionAndAmount
,
634 const EditorDOMPoint
& aCaretPoint
,
635 nsRange
& aRangeToDelete
,
636 const Element
& aEditingHost
) const {
638 case Mode::JoinCurrentBlock
: {
639 nsresult rv
= ComputeRangeToDeleteAtCurrentBlockBoundary(
640 aHTMLEditor
, aCaretPoint
, aRangeToDelete
, aEditingHost
);
641 NS_WARNING_ASSERTION(
643 "AutoBlockElementsJoiner::"
644 "ComputeRangeToDeleteAtCurrentBlockBoundary() failed");
647 case Mode::JoinOtherBlock
: {
648 nsresult rv
= ComputeRangeToDeleteAtOtherBlockBoundary(
649 aHTMLEditor
, aDirectionAndAmount
, aCaretPoint
, aRangeToDelete
,
651 NS_WARNING_ASSERTION(
653 "AutoBlockElementsJoiner::"
654 "ComputeRangeToDeleteAtOtherBlockBoundary() failed");
657 case Mode::DeleteBRElement
: {
658 nsresult rv
= ComputeRangeToDeleteBRElement(aRangeToDelete
);
659 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
660 "AutoBlockElementsJoiner::"
661 "ComputeRangeToDeleteBRElement() failed");
664 case Mode::JoinBlocksInSameParent
:
665 case Mode::DeleteContentInRange
:
666 case Mode::DeleteNonCollapsedRange
:
667 MOZ_ASSERT_UNREACHABLE(
668 "This mode should be handled in the other "
669 "ComputeRangesToDelete()");
670 return NS_ERROR_UNEXPECTED
;
671 case Mode::NotInitialized
:
674 return NS_ERROR_NOT_IMPLEMENTED
;
678 * Run() executes the joining.
680 * @param aHTMLEditor The HTML editor.
681 * @param aDirectionAndAmount Direction of the deletion.
682 * @param aStripWrappers Whether delete or keep new empty
684 * @param aRangeToDelete The range to delete. Must not be
686 * @param aSelectionWasCollapsed Whether selection was or was not
687 * collapsed when starting to handle
690 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT Result
<EditActionResult
, nsresult
> Run(
691 HTMLEditor
& aHTMLEditor
, nsIEditor::EDirection aDirectionAndAmount
,
692 nsIEditor::EStripWrappers aStripWrappers
, nsRange
& aRangeToDelete
,
693 AutoDeleteRangesHandler::SelectionWasCollapsed aSelectionWasCollapsed
,
694 const Element
& aEditingHost
) {
696 case Mode::JoinCurrentBlock
:
697 case Mode::JoinOtherBlock
:
698 case Mode::DeleteBRElement
:
699 MOZ_ASSERT_UNREACHABLE(
700 "This mode should be handled in the other Run()");
701 return Err(NS_ERROR_UNEXPECTED
);
702 case Mode::JoinBlocksInSameParent
: {
703 Result
<EditActionResult
, nsresult
> result
=
704 JoinBlockElementsInSameParent(
705 aHTMLEditor
, aDirectionAndAmount
, aStripWrappers
,
706 aRangeToDelete
, aSelectionWasCollapsed
, aEditingHost
);
707 NS_WARNING_ASSERTION(result
.isOk(),
708 "AutoBlockElementsJoiner::"
709 "JoinBlockElementsInSameParent() failed");
712 case Mode::DeleteContentInRange
: {
713 Result
<EditActionResult
, nsresult
> result
= DeleteContentInRange(
714 aHTMLEditor
, aDirectionAndAmount
, aStripWrappers
, aRangeToDelete
);
715 NS_WARNING_ASSERTION(
717 "AutoBlockElementsJoiner::DeleteContentInRange() failed");
720 case Mode::DeleteNonCollapsedRange
: {
721 Result
<EditActionResult
, nsresult
> result
=
722 HandleDeleteNonCollapsedRange(
723 aHTMLEditor
, aDirectionAndAmount
, aStripWrappers
,
724 aRangeToDelete
, aSelectionWasCollapsed
, aEditingHost
);
725 NS_WARNING_ASSERTION(result
.isOk(),
726 "AutoBlockElementsJoiner::"
727 "HandleDeleteNonCollapsedRange() failed");
730 case Mode::NotInitialized
:
731 MOZ_ASSERT_UNREACHABLE(
732 "Call Run() after calling a preparation method");
733 return EditActionResult::IgnoredResult();
735 return Err(NS_ERROR_NOT_INITIALIZED
);
738 nsresult
ComputeRangeToDelete(
739 const HTMLEditor
& aHTMLEditor
,
740 nsIEditor::EDirection aDirectionAndAmount
, nsRange
& aRangeToDelete
,
741 AutoDeleteRangesHandler::SelectionWasCollapsed aSelectionWasCollapsed
,
742 const Element
& aEditingHost
) const {
744 case Mode::JoinCurrentBlock
:
745 case Mode::JoinOtherBlock
:
746 case Mode::DeleteBRElement
:
747 MOZ_ASSERT_UNREACHABLE(
748 "This mode should be handled in the other "
749 "ComputeRangesToDelete()");
750 return NS_ERROR_UNEXPECTED
;
751 case Mode::JoinBlocksInSameParent
: {
752 nsresult rv
= ComputeRangeToJoinBlockElementsInSameParent(
753 aHTMLEditor
, aDirectionAndAmount
, aRangeToDelete
, aEditingHost
);
754 NS_WARNING_ASSERTION(
756 "AutoBlockElementsJoiner::"
757 "ComputeRangesToJoinBlockElementsInSameParent() failed");
760 case Mode::DeleteContentInRange
: {
761 nsresult rv
= ComputeRangeToDeleteContentInRange(
762 aHTMLEditor
, aDirectionAndAmount
, aRangeToDelete
, aEditingHost
);
763 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
764 "AutoBlockElementsJoiner::"
765 "ComputeRangesToDeleteContentInRanges() failed");
768 case Mode::DeleteNonCollapsedRange
: {
769 nsresult rv
= ComputeRangeToDeleteNonCollapsedRange(
770 aHTMLEditor
, aDirectionAndAmount
, aRangeToDelete
,
771 aSelectionWasCollapsed
, aEditingHost
);
772 NS_WARNING_ASSERTION(
774 "AutoBlockElementsJoiner::"
775 "ComputeRangesToDeleteNonCollapsedRanges() failed");
778 case Mode::NotInitialized
:
779 MOZ_ASSERT_UNREACHABLE(
780 "Call ComputeRangesToDelete() after calling a preparation "
782 return NS_ERROR_NOT_INITIALIZED
;
784 return NS_ERROR_NOT_INITIALIZED
;
787 nsIContent
* GetLeafContentInOtherBlockElement() const {
788 MOZ_ASSERT(mMode
== Mode::JoinOtherBlock
);
789 return mLeafContentInOtherBlock
;
793 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT Result
<EditActionResult
, nsresult
>
794 HandleDeleteAtCurrentBlockBoundary(
795 HTMLEditor
& aHTMLEditor
, nsIEditor::EDirection aDirectionAndAmount
,
796 const EditorDOMPoint
& aCaretPoint
, const Element
& aEditingHost
);
797 nsresult
ComputeRangeToDeleteAtCurrentBlockBoundary(
798 const HTMLEditor
& aHTMLEditor
, const EditorDOMPoint
& aCaretPoint
,
799 nsRange
& aRangeToDelete
, const Element
& aEditingHost
) const;
800 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT Result
<EditActionResult
, nsresult
>
801 HandleDeleteAtOtherBlockBoundary(HTMLEditor
& aHTMLEditor
,
802 nsIEditor::EDirection aDirectionAndAmount
,
803 nsIEditor::EStripWrappers aStripWrappers
,
804 const EditorDOMPoint
& aCaretPoint
,
805 nsRange
& aRangeToDelete
,
806 const Element
& aEditingHost
);
807 // FYI: This method may modify selection, but it won't cause running
808 // script because of `AutoHideSelectionChanges` which blocks
809 // selection change listeners and the selection change event
811 MOZ_CAN_RUN_SCRIPT_BOUNDARY nsresult
812 ComputeRangeToDeleteAtOtherBlockBoundary(
813 const HTMLEditor
& aHTMLEditor
,
814 nsIEditor::EDirection aDirectionAndAmount
,
815 const EditorDOMPoint
& aCaretPoint
, nsRange
& aRangeToDelete
,
816 const Element
& aEditingHost
) const;
817 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT Result
<EditActionResult
, nsresult
>
818 JoinBlockElementsInSameParent(
819 HTMLEditor
& aHTMLEditor
, nsIEditor::EDirection aDirectionAndAmount
,
820 nsIEditor::EStripWrappers aStripWrappers
, nsRange
& aRangeToDelete
,
821 AutoDeleteRangesHandler::SelectionWasCollapsed aSelectionWasCollapsed
,
822 const Element
& aEditingHost
);
823 nsresult
ComputeRangeToJoinBlockElementsInSameParent(
824 const HTMLEditor
& aHTMLEditor
,
825 nsIEditor::EDirection aDirectionAndAmount
, nsRange
& aRangeToDelete
,
826 const Element
& aEditingHost
) const;
827 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT Result
<EditActionResult
, nsresult
>
828 DeleteBRElement(HTMLEditor
& aHTMLEditor
,
829 nsIEditor::EDirection aDirectionAndAmount
,
830 const Element
& aEditingHost
);
831 nsresult
ComputeRangeToDeleteBRElement(nsRange
& aRangeToDelete
) const;
832 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT Result
<EditActionResult
, nsresult
>
833 DeleteContentInRange(HTMLEditor
& aHTMLEditor
,
834 nsIEditor::EDirection aDirectionAndAmount
,
835 nsIEditor::EStripWrappers aStripWrappers
,
836 nsRange
& aRangeToDelete
);
837 nsresult
ComputeRangeToDeleteContentInRange(
838 const HTMLEditor
& aHTMLEditor
,
839 nsIEditor::EDirection aDirectionAndAmount
, nsRange
& aRange
,
840 const Element
& aEditingHost
) const;
841 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT Result
<EditActionResult
, nsresult
>
842 HandleDeleteNonCollapsedRange(
843 HTMLEditor
& aHTMLEditor
, nsIEditor::EDirection aDirectionAndAmount
,
844 nsIEditor::EStripWrappers aStripWrappers
, nsRange
& aRangeToDelete
,
845 AutoDeleteRangesHandler::SelectionWasCollapsed aSelectionWasCollapsed
,
846 const Element
& aEditingHost
);
847 nsresult
ComputeRangeToDeleteNonCollapsedRange(
848 const HTMLEditor
& aHTMLEditor
,
849 nsIEditor::EDirection aDirectionAndAmount
, nsRange
& aRangeToDelete
,
850 AutoDeleteRangesHandler::SelectionWasCollapsed aSelectionWasCollapsed
,
851 const Element
& aEditingHost
) const;
854 * JoinNodesDeepWithTransaction() joins aLeftNode and aRightNode "deeply".
855 * First, they are joined simply, then, new right node is assumed as the
856 * child at length of the left node before joined and new left node is
857 * assumed as its previous sibling. Then, they will be joined again.
858 * And then, these steps are repeated.
860 * @param aLeftContent The node which will be removed form the tree.
861 * @param aRightContent The node which will be inserted the contents of
863 * @return The point of the first child of the last right
864 * node. The result is always set if this succeeded.
866 MOZ_CAN_RUN_SCRIPT Result
<EditorDOMPoint
, nsresult
>
867 JoinNodesDeepWithTransaction(HTMLEditor
& aHTMLEditor
,
868 nsIContent
& aLeftContent
,
869 nsIContent
& aRightContent
);
872 * DeleteNodesEntirelyInRangeButKeepTableStructure() removes nodes which are
873 * entirely in aRange. Howevers, if some nodes are part of a table,
874 * removes all children of them instead. I.e., this does not make damage to
875 * table structure at the range, but may remove table entirely if it's
878 * @return true if inclusive ancestor block elements at
879 * start and end of the range should be joined.
881 MOZ_CAN_RUN_SCRIPT Result
<bool, nsresult
>
882 DeleteNodesEntirelyInRangeButKeepTableStructure(
883 HTMLEditor
& aHTMLEditor
, nsRange
& aRange
,
884 AutoDeleteRangesHandler::SelectionWasCollapsed aSelectionWasCollapsed
);
885 bool NeedsToJoinNodesAfterDeleteNodesEntirelyInRangeButKeepTableStructure(
886 const HTMLEditor
& aHTMLEditor
,
887 const nsTArray
<OwningNonNull
<nsIContent
>>& aArrayOfContents
,
888 AutoDeleteRangesHandler::SelectionWasCollapsed aSelectionWasCollapsed
)
890 Result
<bool, nsresult
>
891 ComputeRangeToDeleteNodesEntirelyInRangeButKeepTableStructure(
892 const HTMLEditor
& aHTMLEditor
, nsRange
& aRange
,
893 AutoDeleteRangesHandler::SelectionWasCollapsed aSelectionWasCollapsed
)
897 * DeleteContentButKeepTableStructure() removes aContent if it's an element
898 * which is part of a table structure. If it's a part of table structure,
899 * removes its all children recursively. I.e., this may delete all of a
900 * table, but won't break table structure partially.
902 * @param aContent The content which or whose all children should
905 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT nsresult
906 DeleteContentButKeepTableStructure(HTMLEditor
& aHTMLEditor
,
907 nsIContent
& aContent
);
910 * DeleteTextAtStartAndEndOfRange() removes text if start and/or end of
911 * aRange is in a text node.
913 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT nsresult
914 DeleteTextAtStartAndEndOfRange(HTMLEditor
& aHTMLEditor
, nsRange
& aRange
);
916 class MOZ_STACK_CLASS AutoInclusiveAncestorBlockElementsJoiner final
{
918 AutoInclusiveAncestorBlockElementsJoiner() = delete;
919 AutoInclusiveAncestorBlockElementsJoiner(
920 nsIContent
& aInclusiveDescendantOfLeftBlockElement
,
921 nsIContent
& aInclusiveDescendantOfRightBlockElement
)
922 : mInclusiveDescendantOfLeftBlockElement(
923 aInclusiveDescendantOfLeftBlockElement
),
924 mInclusiveDescendantOfRightBlockElement(
925 aInclusiveDescendantOfRightBlockElement
),
926 mCanJoinBlocks(false),
927 mFallbackToDeleteLeafContent(false) {}
929 bool IsSet() const { return mLeftBlockElement
&& mRightBlockElement
; }
930 bool IsSameBlockElement() const {
931 return mLeftBlockElement
&& mLeftBlockElement
== mRightBlockElement
;
934 const EditorDOMPoint
& PointRefToPutCaret() const {
935 return mPointToPutCaret
;
939 * Prepare for joining inclusive ancestor block elements. When this
940 * returns false, the deletion should be canceled.
942 Result
<bool, nsresult
> Prepare(const HTMLEditor
& aHTMLEditor
,
943 const Element
& aEditingHost
);
946 * When this returns true, this can join the blocks with `Run()`.
948 bool CanJoinBlocks() const { return mCanJoinBlocks
; }
951 * When this returns true, `Run()` must return "ignored" so that
952 * caller can skip calling `Run()`. This is available only when
953 * `CanJoinBlocks()` returns `true`.
954 * TODO: This should be merged into `CanJoinBlocks()` in the future.
956 bool ShouldDeleteLeafContentInstead() const {
957 MOZ_ASSERT(CanJoinBlocks());
958 return mFallbackToDeleteLeafContent
;
962 * ComputeRangesToDelete() extends aRangeToDelete includes the element
963 * boundaries between joining blocks. If they won't be joined, this
964 * collapses the range to aCaretPoint.
966 nsresult
ComputeRangeToDelete(const HTMLEditor
& aHTMLEditor
,
967 const EditorDOMPoint
& aCaretPoint
,
968 nsRange
& aRangeToDelete
) const;
971 * Join inclusive ancestor block elements which are found by preceding
973 * The right element is always joined to the left element.
974 * If the elements are the same type and not nested within each other,
975 * JoinEditableNodesWithTransaction() is called (example, joining two
976 * list items together into one).
977 * If the elements are not the same type, or one is a descendant of the
978 * other, we instead destroy the right block placing its children into
981 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT Result
<EditActionResult
, nsresult
> Run(
982 HTMLEditor
& aHTMLEditor
, const Element
& aEditingHost
);
986 * This method returns true when
987 * `MergeFirstLineOfRightBlockElementIntoDescendantLeftBlockElement()`,
988 * `MergeFirstLineOfRightBlockElementIntoAncestorLeftBlockElement()` and
989 * `MergeFirstLineOfRightBlockElementIntoLeftBlockElement()` handle it
990 * with the `if` block of their main blocks.
992 bool CanMergeLeftAndRightBlockElements() const {
996 // `MergeFirstLineOfRightBlockElementIntoDescendantLeftBlockElement()`
997 if (mPointContainingTheOtherBlockElement
.GetContainer() ==
998 mRightBlockElement
) {
999 return mNewListElementTagNameOfRightListElement
.isSome();
1001 // `MergeFirstLineOfRightBlockElementIntoAncestorLeftBlockElement()`
1002 if (mPointContainingTheOtherBlockElement
.GetContainer() ==
1003 mLeftBlockElement
) {
1004 return mNewListElementTagNameOfRightListElement
.isSome() &&
1005 !mRightBlockElement
->GetChildCount();
1007 MOZ_ASSERT(!mPointContainingTheOtherBlockElement
.IsSet());
1008 // `MergeFirstLineOfRightBlockElementIntoLeftBlockElement()`
1009 return mNewListElementTagNameOfRightListElement
.isSome() ||
1010 mLeftBlockElement
->NodeInfo()->NameAtom() ==
1011 mRightBlockElement
->NodeInfo()->NameAtom();
1014 OwningNonNull
<nsIContent
> mInclusiveDescendantOfLeftBlockElement
;
1015 OwningNonNull
<nsIContent
> mInclusiveDescendantOfRightBlockElement
;
1016 RefPtr
<Element
> mLeftBlockElement
;
1017 RefPtr
<Element
> mRightBlockElement
;
1018 Maybe
<nsAtom
*> mNewListElementTagNameOfRightListElement
;
1019 EditorDOMPoint mPointContainingTheOtherBlockElement
;
1020 EditorDOMPoint mPointToPutCaret
;
1021 RefPtr
<dom::HTMLBRElement
> mPrecedingInvisibleBRElement
;
1022 bool mCanJoinBlocks
;
1023 bool mFallbackToDeleteLeafContent
;
1024 }; // HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
1025 // AutoInclusiveAncestorBlockElementsJoiner
1031 JoinBlocksInSameParent
,
1033 DeleteContentInRange
,
1034 DeleteNonCollapsedRange
,
1036 AutoDeleteRangesHandler
* mDeleteRangesHandler
;
1037 const AutoDeleteRangesHandler
& mDeleteRangesHandlerConst
;
1038 nsCOMPtr
<nsIContent
> mLeftContent
;
1039 nsCOMPtr
<nsIContent
> mRightContent
;
1040 nsCOMPtr
<nsIContent
> mLeafContentInOtherBlock
;
1041 // mSkippedInvisibleContents stores all content nodes which are skipped at
1042 // scanning mLeftContent and mRightContent. The content nodes should be
1043 // removed at deletion.
1044 AutoTArray
<OwningNonNull
<nsIContent
>, 8> mSkippedInvisibleContents
;
1045 RefPtr
<dom::HTMLBRElement
> mBRElement
;
1046 Mode mMode
= Mode::NotInitialized
;
1047 }; // HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner
1049 class MOZ_STACK_CLASS AutoEmptyBlockAncestorDeleter final
{
1052 * ScanEmptyBlockInclusiveAncestor() scans an inclusive ancestor element
1053 * which is empty and a block element. Then, stores the result and
1054 * returns the found empty block element.
1056 * @param aHTMLEditor The HTMLEditor.
1057 * @param aStartContent Start content to look for empty ancestors.
1059 [[nodiscard
]] Element
* ScanEmptyBlockInclusiveAncestor(
1060 const HTMLEditor
& aHTMLEditor
, nsIContent
& aStartContent
);
1063 * ComputeTargetRanges() computes "target ranges" for deleting
1064 * `mEmptyInclusiveAncestorBlockElement`.
1066 nsresult
ComputeTargetRanges(const HTMLEditor
& aHTMLEditor
,
1067 nsIEditor::EDirection aDirectionAndAmount
,
1068 const Element
& aEditingHost
,
1069 AutoRangeArray
& aRangesToDelete
) const;
1072 * Deletes found empty block element by `ScanEmptyBlockInclusiveAncestor()`.
1073 * If found one is a list item element, calls
1074 * `MaybeInsertBRElementBeforeEmptyListItemElement()` before deleting
1075 * the list item element.
1076 * If found empty ancestor is not a list item element,
1077 * `GetNewCaretPosition()` will be called to determine new caret position.
1078 * Finally, removes the empty block ancestor.
1080 * @param aHTMLEditor The HTMLEditor.
1081 * @param aDirectionAndAmount If found empty ancestor block is a list item
1082 * element, this is ignored. Otherwise:
1083 * - If eNext, eNextWord or eToEndOfLine,
1084 * collapse Selection to after found empty
1086 * - If ePrevious, ePreviousWord or
1087 * eToBeginningOfLine, collapse Selection to
1088 * end of previous editable node.
1089 * - Otherwise, eNone is allowed but does
1092 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT Result
<EditActionResult
, nsresult
> Run(
1093 HTMLEditor
& aHTMLEditor
, nsIEditor::EDirection aDirectionAndAmount
);
1097 * MaybeReplaceSubListWithNewListItem() replaces
1098 * mEmptyInclusiveAncestorBlockElement with new list item element
1099 * (containing <br>) if:
1100 * - mEmptyInclusiveAncestorBlockElement is a list element
1101 * - The parent of mEmptyInclusiveAncestorBlockElement is a list element
1102 * - The parent becomes empty after deletion
1103 * If this does not perform the replacement, returns "ignored".
1105 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT Result
<EditActionResult
, nsresult
>
1106 MaybeReplaceSubListWithNewListItem(HTMLEditor
& aHTMLEditor
);
1109 * MaybeInsertBRElementBeforeEmptyListItemElement() inserts a `<br>` element
1110 * if `mEmptyInclusiveAncestorBlockElement` is a list item element which
1111 * is first editable element in its parent, and its grand parent is not a
1112 * list element, inserts a `<br>` element before the empty list item.
1114 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT Result
<RefPtr
<Element
>, nsresult
>
1115 MaybeInsertBRElementBeforeEmptyListItemElement(HTMLEditor
& aHTMLEditor
);
1118 * GetNewCaretPosition() returns new caret position after deleting
1119 * `mEmptyInclusiveAncestorBlockElement`.
1121 [[nodiscard
]] Result
<CaretPoint
, nsresult
> GetNewCaretPosition(
1122 const HTMLEditor
& aHTMLEditor
,
1123 nsIEditor::EDirection aDirectionAndAmount
) const;
1125 RefPtr
<Element
> mEmptyInclusiveAncestorBlockElement
;
1126 }; // HTMLEditor::AutoDeleteRangesHandler::AutoEmptyBlockAncestorDeleter
1128 const AutoDeleteRangesHandler
* const mParent
;
1129 nsIEditor::EDirection mOriginalDirectionAndAmount
;
1130 nsIEditor::EStripWrappers mOriginalStripWrappers
;
1131 }; // HTMLEditor::AutoDeleteRangesHandler
1133 nsresult
HTMLEditor::ComputeTargetRanges(
1134 nsIEditor::EDirection aDirectionAndAmount
,
1135 AutoRangeArray
& aRangesToDelete
) const {
1136 MOZ_ASSERT(IsEditActionDataAvailable());
1138 Element
* editingHost
= ComputeEditingHost();
1140 aRangesToDelete
.RemoveAllRanges();
1141 return NS_ERROR_EDITOR_NO_EDITABLE_RANGE
;
1144 // First check for table selection mode. If so, hand off to table editor.
1145 SelectedTableCellScanner
scanner(aRangesToDelete
);
1146 if (scanner
.IsInTableCellSelectionMode()) {
1147 // If it's in table cell selection mode, we'll delete all childen in
1148 // the all selected table cell elements,
1149 if (scanner
.ElementsRef().Length() == aRangesToDelete
.Ranges().Length()) {
1152 // but will ignore all ranges which does not select a table cell.
1153 size_t removedRanges
= 0;
1154 for (size_t i
= 1; i
< scanner
.ElementsRef().Length(); i
++) {
1155 if (HTMLEditUtils::GetTableCellElementIfOnlyOneSelected(
1156 aRangesToDelete
.Ranges()[i
- removedRanges
]) !=
1157 scanner
.ElementsRef()[i
]) {
1158 // XXX Need to manage anchor-focus range too!
1159 aRangesToDelete
.Ranges().RemoveElementAt(i
- removedRanges
);
1166 aRangesToDelete
.EnsureOnlyEditableRanges(*editingHost
);
1167 if (aRangesToDelete
.Ranges().IsEmpty()) {
1169 "There is no range which we can delete entire of or around the caret");
1170 return NS_ERROR_EDITOR_NO_EDITABLE_RANGE
;
1172 AutoDeleteRangesHandler deleteHandler
;
1173 // Should we delete target ranges which cannot delete actually?
1174 nsresult rv
= deleteHandler
.ComputeRangesToDelete(
1175 *this, aDirectionAndAmount
, aRangesToDelete
, *editingHost
);
1176 NS_WARNING_ASSERTION(
1178 "AutoDeleteRangesHandler::ComputeRangesToDelete() failed");
1182 Result
<EditActionResult
, nsresult
> HTMLEditor::HandleDeleteSelection(
1183 nsIEditor::EDirection aDirectionAndAmount
,
1184 nsIEditor::EStripWrappers aStripWrappers
) {
1185 MOZ_ASSERT(IsEditActionDataAvailable());
1186 MOZ_ASSERT(aStripWrappers
== nsIEditor::eStrip
||
1187 aStripWrappers
== nsIEditor::eNoStrip
);
1189 if (MOZ_UNLIKELY(!SelectionRef().RangeCount())) {
1190 return Err(NS_ERROR_EDITOR_NO_EDITABLE_RANGE
);
1193 RefPtr
<Element
> editingHost
= ComputeEditingHost();
1194 if (MOZ_UNLIKELY(!editingHost
)) {
1195 return Err(NS_ERROR_EDITOR_NO_EDITABLE_RANGE
);
1198 // Remember that we did a selection deletion. Used by
1199 // CreateStyleForInsertText()
1200 TopLevelEditSubActionDataRef().mDidDeleteSelection
= true;
1202 if (MOZ_UNLIKELY(IsEmpty())) {
1203 return EditActionResult::CanceledResult();
1206 // First check for table selection mode. If so, hand off to table editor.
1207 if (HTMLEditUtils::IsInTableCellSelectionMode(SelectionRef())) {
1208 nsresult rv
= DeleteTableCellContentsWithTransaction();
1209 if (NS_WARN_IF(Destroyed())) {
1210 return Err(NS_ERROR_EDITOR_DESTROYED
);
1212 if (NS_FAILED(rv
)) {
1213 NS_WARNING("HTMLEditor::DeleteTableCellContentsWithTransaction() failed");
1216 return EditActionResult::HandledResult();
1219 AutoRangeArray
rangesToDelete(SelectionRef());
1220 rangesToDelete
.EnsureOnlyEditableRanges(*editingHost
);
1221 if (MOZ_UNLIKELY(rangesToDelete
.Ranges().IsEmpty())) {
1223 "There is no range which we can delete entire the ranges or around the "
1225 return Err(NS_ERROR_EDITOR_NO_EDITABLE_RANGE
);
1227 AutoDeleteRangesHandler deleteHandler
;
1228 Result
<EditActionResult
, nsresult
> result
= deleteHandler
.Run(
1229 *this, aDirectionAndAmount
, aStripWrappers
, rangesToDelete
, *editingHost
);
1230 if (MOZ_UNLIKELY(result
.isErr()) || result
.inspect().Canceled()) {
1231 NS_WARNING_ASSERTION(result
.isOk(),
1232 "AutoDeleteRangesHandler::Run() failed");
1236 // XXX At here, selection may have no range because of mutation event
1237 // listeners can do anything so that we should just return NS_OK instead
1238 // of returning error.
1239 const auto atNewStartOfSelection
=
1240 GetFirstSelectionStartPoint
<EditorDOMPoint
>();
1241 if (NS_WARN_IF(!atNewStartOfSelection
.IsSet())) {
1242 return Err(NS_ERROR_FAILURE
);
1244 if (atNewStartOfSelection
.IsInContentNode()) {
1245 nsresult rv
= DeleteMostAncestorMailCiteElementIfEmpty(
1246 MOZ_KnownLive(*atNewStartOfSelection
.ContainerAs
<nsIContent
>()));
1247 if (NS_FAILED(rv
)) {
1249 "HTMLEditor::DeleteMostAncestorMailCiteElementIfEmpty() failed");
1253 return EditActionResult::HandledResult();
1256 nsresult
HTMLEditor::AutoDeleteRangesHandler::ComputeRangesToDelete(
1257 const HTMLEditor
& aHTMLEditor
, nsIEditor::EDirection aDirectionAndAmount
,
1258 AutoRangeArray
& aRangesToDelete
, const Element
& aEditingHost
) {
1259 MOZ_ASSERT(aHTMLEditor
.IsEditActionDataAvailable());
1260 MOZ_ASSERT(!aRangesToDelete
.Ranges().IsEmpty());
1262 mOriginalDirectionAndAmount
= aDirectionAndAmount
;
1263 mOriginalStripWrappers
= nsIEditor::eNoStrip
;
1265 if (aHTMLEditor
.mPaddingBRElementForEmptyEditor
) {
1266 nsresult rv
= aRangesToDelete
.Collapse(
1267 EditorRawDOMPoint(aHTMLEditor
.mPaddingBRElementForEmptyEditor
));
1268 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
), "AutoRangeArray::Collapse() failed");
1272 SelectionWasCollapsed selectionWasCollapsed
= aRangesToDelete
.IsCollapsed()
1273 ? SelectionWasCollapsed::Yes
1274 : SelectionWasCollapsed::No
;
1275 if (selectionWasCollapsed
== SelectionWasCollapsed::Yes
) {
1276 const auto startPoint
=
1277 aRangesToDelete
.GetFirstRangeStartPoint
<EditorDOMPoint
>();
1278 if (NS_WARN_IF(!startPoint
.IsSet())) {
1279 return NS_ERROR_FAILURE
;
1281 RefPtr
<Element
> editingHost
= aHTMLEditor
.ComputeEditingHost();
1282 if (NS_WARN_IF(!editingHost
)) {
1283 return NS_ERROR_FAILURE
;
1285 if (startPoint
.IsInContentNode()) {
1286 AutoEmptyBlockAncestorDeleter deleter
;
1287 if (deleter
.ScanEmptyBlockInclusiveAncestor(
1288 aHTMLEditor
, *startPoint
.ContainerAs
<nsIContent
>())) {
1289 nsresult rv
= deleter
.ComputeTargetRanges(
1290 aHTMLEditor
, aDirectionAndAmount
, *editingHost
, aRangesToDelete
);
1291 NS_WARNING_ASSERTION(
1293 "AutoEmptyBlockAncestorDeleter::ComputeTargetRanges() failed");
1298 // We shouldn't update caret bidi level right now, but we need to check
1299 // whether the deletion will be canceled or not.
1300 AutoCaretBidiLevelManager
bidiLevelManager(aHTMLEditor
, aDirectionAndAmount
,
1302 if (bidiLevelManager
.Failed()) {
1304 "EditorBase::AutoCaretBidiLevelManager failed to initialize itself");
1305 return NS_ERROR_FAILURE
;
1307 if (bidiLevelManager
.Canceled()) {
1308 return NS_SUCCESS_DOM_NO_OPERATION
;
1311 // AutoRangeArray::ExtendAnchorFocusRangeFor() will use `nsFrameSelection`
1312 // to extend the range for deletion. But if focus event doesn't receive
1313 // yet, ancestor isn't set. So we must set root element of editor to
1314 // ancestor temporarily.
1315 AutoSetTemporaryAncestorLimiter
autoSetter(
1316 aHTMLEditor
, aHTMLEditor
.SelectionRef(), *startPoint
.GetContainer(),
1319 Result
<nsIEditor::EDirection
, nsresult
> extendResult
=
1320 aRangesToDelete
.ExtendAnchorFocusRangeFor(aHTMLEditor
,
1321 aDirectionAndAmount
);
1322 if (extendResult
.isErr()) {
1323 NS_WARNING("AutoRangeArray::ExtendAnchorFocusRangeFor() failed");
1324 return extendResult
.unwrapErr();
1327 // For compatibility with other browsers, we should set target ranges
1328 // to start from and/or end after an atomic content rather than start
1329 // from preceding text node end nor end at following text node start.
1330 Result
<bool, nsresult
> shrunkenResult
=
1331 aRangesToDelete
.ShrinkRangesIfStartFromOrEndAfterAtomicContent(
1332 aHTMLEditor
, aDirectionAndAmount
,
1333 AutoRangeArray::IfSelectingOnlyOneAtomicContent::Collapse
,
1335 if (shrunkenResult
.isErr()) {
1337 "AutoRangeArray::ShrinkRangesIfStartFromOrEndAfterAtomicContent() "
1339 return shrunkenResult
.unwrapErr();
1342 if (!shrunkenResult
.inspect() || !aRangesToDelete
.IsCollapsed()) {
1343 aDirectionAndAmount
= extendResult
.unwrap();
1346 if (aDirectionAndAmount
== nsIEditor::eNone
) {
1347 MOZ_ASSERT(aRangesToDelete
.Ranges().Length() == 1);
1348 if (!CanFallbackToDeleteRangesWithTransaction(aRangesToDelete
)) {
1349 // XXX In this case, do we need to modify the range again?
1350 return NS_SUCCESS_DOM_NO_OPERATION
;
1352 nsresult rv
= FallbackToComputeRangesToDeleteRangesWithTransaction(
1353 aHTMLEditor
, aRangesToDelete
, *editingHost
);
1354 NS_WARNING_ASSERTION(
1356 "AutoDeleteRangesHandler::"
1357 "FallbackToComputeRangesToDeleteRangesWithTransaction() failed");
1361 if (aRangesToDelete
.IsCollapsed()) {
1362 const auto caretPoint
=
1363 aRangesToDelete
.GetFirstRangeStartPoint
<EditorDOMPoint
>();
1364 if (MOZ_UNLIKELY(NS_WARN_IF(!caretPoint
.IsInContentNode()))) {
1365 return NS_ERROR_FAILURE
;
1367 if (!EditorUtils::IsEditableContent(*caretPoint
.ContainerAs
<nsIContent
>(),
1368 EditorType::HTML
)) {
1369 return NS_SUCCESS_DOM_NO_OPERATION
;
1371 WSRunScanner
wsRunScannerAtCaret(
1372 editingHost
, caretPoint
,
1373 BlockInlineCheck::UseComputedDisplayOutsideStyle
);
1374 WSScanResult scanFromCaretPointResult
=
1375 aDirectionAndAmount
== nsIEditor::eNext
1376 ? wsRunScannerAtCaret
.ScanNextVisibleNodeOrBlockBoundaryFrom(
1378 : wsRunScannerAtCaret
.ScanPreviousVisibleNodeOrBlockBoundaryFrom(
1380 if (scanFromCaretPointResult
.Failed()) {
1382 "WSRunScanner::Scan(Next|Previous)VisibleNodeOrBlockBoundaryFrom() "
1384 return NS_ERROR_FAILURE
;
1386 if (!scanFromCaretPointResult
.GetContent()) {
1387 return NS_SUCCESS_DOM_NO_OPERATION
;
1390 if (scanFromCaretPointResult
.ReachedBRElement()) {
1391 if (scanFromCaretPointResult
.BRElementPtr() ==
1392 wsRunScannerAtCaret
.GetEditingHost()) {
1395 if (!EditorUtils::IsEditableContent(
1396 *scanFromCaretPointResult
.BRElementPtr(), EditorType::HTML
)) {
1397 return NS_SUCCESS_DOM_NO_OPERATION
;
1399 if (HTMLEditUtils::IsInvisibleBRElement(
1400 *scanFromCaretPointResult
.BRElementPtr())) {
1401 EditorDOMPoint newCaretPosition
=
1402 aDirectionAndAmount
== nsIEditor::eNext
1403 ? EditorDOMPoint::After(
1404 *scanFromCaretPointResult
.BRElementPtr())
1405 : EditorDOMPoint(scanFromCaretPointResult
.BRElementPtr());
1406 if (NS_WARN_IF(!newCaretPosition
.IsSet())) {
1407 return NS_ERROR_FAILURE
;
1409 AutoHideSelectionChanges
blockSelectionListeners(
1410 aHTMLEditor
.SelectionRef());
1411 nsresult rv
= aHTMLEditor
.CollapseSelectionTo(newCaretPosition
);
1412 if (MOZ_UNLIKELY(NS_FAILED(rv
))) {
1413 NS_WARNING("EditorBase::CollapseSelectionTo() failed");
1414 return NS_ERROR_FAILURE
;
1416 if (NS_WARN_IF(!aHTMLEditor
.SelectionRef().RangeCount())) {
1417 return NS_ERROR_UNEXPECTED
;
1419 aRangesToDelete
.Initialize(aHTMLEditor
.SelectionRef());
1420 AutoDeleteRangesHandler
anotherHandler(this);
1421 rv
= anotherHandler
.ComputeRangesToDelete(
1422 aHTMLEditor
, aDirectionAndAmount
, aRangesToDelete
, aEditingHost
);
1423 NS_WARNING_ASSERTION(
1425 "Recursive AutoDeleteRangesHandler::ComputeRangesToDelete() "
1428 rv
= aHTMLEditor
.CollapseSelectionTo(caretPoint
);
1429 if (MOZ_UNLIKELY(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
1431 "EditorBase::CollapseSelectionTo() caused destroying the "
1433 return NS_ERROR_EDITOR_DESTROYED
;
1435 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
1436 "EditorBase::CollapseSelectionTo() failed to "
1437 "restore original selection, but ignored");
1439 MOZ_ASSERT(aRangesToDelete
.Ranges().Length() == 1);
1440 // If the range is collapsed, there is no content which should
1441 // be removed together. In this case, only the invisible `<br>`
1442 // element should be selected.
1443 if (aRangesToDelete
.IsCollapsed()) {
1444 nsresult rv
= aRangesToDelete
.SelectNode(
1445 *scanFromCaretPointResult
.BRElementPtr());
1446 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
1447 "AutoRangeArray::SelectNode() failed");
1451 // Otherwise, extend the range to contain the invisible `<br>`
1453 if (EditorRawDOMPoint(scanFromCaretPointResult
.BRElementPtr())
1456 .GetFirstRangeStartPoint
<EditorRawDOMPoint
>())) {
1457 nsresult rv
= aRangesToDelete
.FirstRangeRef()->SetStartAndEnd(
1458 EditorRawDOMPoint(scanFromCaretPointResult
.BRElementPtr())
1459 .ToRawRangeBoundary(),
1460 aRangesToDelete
.FirstRangeRef()->EndRef());
1461 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
1462 "nsRange::SetStartAndEnd() failed");
1465 if (aRangesToDelete
.GetFirstRangeEndPoint
<EditorRawDOMPoint
>()
1466 .IsBefore(EditorRawDOMPoint::After(
1467 *scanFromCaretPointResult
.BRElementPtr()))) {
1468 nsresult rv
= aRangesToDelete
.FirstRangeRef()->SetStartAndEnd(
1469 aRangesToDelete
.FirstRangeRef()->StartRef(),
1470 EditorRawDOMPoint::After(
1471 *scanFromCaretPointResult
.BRElementPtr())
1472 .ToRawRangeBoundary());
1473 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
1474 "nsRange::SetStartAndEnd() failed");
1477 NS_WARNING("Was the invisible `<br>` element selected?");
1482 nsresult rv
= ComputeRangesToDeleteAroundCollapsedRanges(
1483 aHTMLEditor
, aDirectionAndAmount
, aRangesToDelete
,
1484 wsRunScannerAtCaret
, scanFromCaretPointResult
, aEditingHost
);
1485 NS_WARNING_ASSERTION(
1487 "AutoDeleteRangesHandler::ComputeRangesToDeleteAroundCollapsedRanges("
1493 nsresult rv
= ComputeRangesToDeleteNonCollapsedRanges(
1494 aHTMLEditor
, aDirectionAndAmount
, aRangesToDelete
, selectionWasCollapsed
,
1496 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
1497 "AutoDeleteRangesHandler::"
1498 "ComputeRangesToDeleteNonCollapsedRanges() failed");
1502 Result
<EditActionResult
, nsresult
> HTMLEditor::AutoDeleteRangesHandler::Run(
1503 HTMLEditor
& aHTMLEditor
, nsIEditor::EDirection aDirectionAndAmount
,
1504 nsIEditor::EStripWrappers aStripWrappers
, AutoRangeArray
& aRangesToDelete
,
1505 const Element
& aEditingHost
) {
1506 MOZ_ASSERT(aHTMLEditor
.IsEditActionDataAvailable());
1507 MOZ_ASSERT(aStripWrappers
== nsIEditor::eStrip
||
1508 aStripWrappers
== nsIEditor::eNoStrip
);
1509 MOZ_ASSERT(!aRangesToDelete
.Ranges().IsEmpty());
1511 mOriginalDirectionAndAmount
= aDirectionAndAmount
;
1512 mOriginalStripWrappers
= aStripWrappers
;
1514 if (MOZ_UNLIKELY(aHTMLEditor
.IsEmpty())) {
1515 return EditActionResult::CanceledResult();
1518 // selectionWasCollapsed is used later to determine whether we should join
1519 // blocks in HandleDeleteNonCollapsedRanges(). We don't really care about
1520 // collapsed because it will be modified by
1521 // AutoRangeArray::ExtendAnchorFocusRangeFor() later.
1522 // AutoBlockElementsJoiner::AutoInclusiveAncestorBlockElementsJoiner should
1523 // happen if the original selection is collapsed and the cursor is at the end
1524 // of a block element, in which case
1525 // AutoRangeArray::ExtendAnchorFocusRangeFor() would always make the selection
1527 SelectionWasCollapsed selectionWasCollapsed
= aRangesToDelete
.IsCollapsed()
1528 ? SelectionWasCollapsed::Yes
1529 : SelectionWasCollapsed::No
;
1531 if (selectionWasCollapsed
== SelectionWasCollapsed::Yes
) {
1532 const auto startPoint
=
1533 aRangesToDelete
.GetFirstRangeStartPoint
<EditorDOMPoint
>();
1534 if (NS_WARN_IF(!startPoint
.IsSet())) {
1535 return Err(NS_ERROR_FAILURE
);
1538 // If we are inside an empty block, delete it.
1539 if (startPoint
.IsInContentNode()) {
1541 nsMutationGuard debugMutation
;
1542 #endif // #ifdef DEBUG
1543 AutoEmptyBlockAncestorDeleter deleter
;
1544 if (deleter
.ScanEmptyBlockInclusiveAncestor(
1545 aHTMLEditor
, *startPoint
.ContainerAs
<nsIContent
>())) {
1546 Result
<EditActionResult
, nsresult
> result
=
1547 deleter
.Run(aHTMLEditor
, aDirectionAndAmount
);
1548 if (MOZ_UNLIKELY(result
.isErr()) || result
.inspect().Handled()) {
1549 NS_WARNING_ASSERTION(result
.isOk(),
1550 "AutoEmptyBlockAncestorDeleter::Run() failed");
1554 MOZ_ASSERT(!debugMutation
.Mutated(0),
1555 "AutoEmptyBlockAncestorDeleter shouldn't modify the DOM tree "
1556 "if it returns not handled nor error");
1559 // Test for distance between caret and text that will be deleted.
1560 // Note that this call modifies `nsFrameSelection` without modifying
1561 // `Selection`. However, it does not have problem for now because
1562 // it'll be referred by `AutoRangeArray::ExtendAnchorFocusRangeFor()`
1563 // before modifying `Selection`.
1564 // XXX This looks odd. `ExtendAnchorFocusRangeFor()` will extend
1565 // anchor-focus range, but here refers the first range.
1566 AutoCaretBidiLevelManager
bidiLevelManager(aHTMLEditor
, aDirectionAndAmount
,
1568 if (MOZ_UNLIKELY(bidiLevelManager
.Failed())) {
1570 "EditorBase::AutoCaretBidiLevelManager failed to initialize itself");
1571 return Err(NS_ERROR_FAILURE
);
1573 bidiLevelManager
.MaybeUpdateCaretBidiLevel(aHTMLEditor
);
1574 if (bidiLevelManager
.Canceled()) {
1575 return EditActionResult::CanceledResult();
1578 // AutoRangeArray::ExtendAnchorFocusRangeFor() will use `nsFrameSelection`
1579 // to extend the range for deletion. But if focus event doesn't receive
1580 // yet, ancestor isn't set. So we must set root element of editor to
1581 // ancestor temporarily.
1582 AutoSetTemporaryAncestorLimiter
autoSetter(
1583 aHTMLEditor
, aHTMLEditor
.SelectionRef(), *startPoint
.GetContainer(),
1586 // Calling `ExtendAnchorFocusRangeFor()` and
1587 // `ShrinkRangesIfStartFromOrEndAfterAtomicContent()` may move caret to
1588 // the container of deleting atomic content. However, it may be different
1589 // from the original caret's container. The original caret container may
1590 // be important to put caret after deletion so that let's cache the
1591 // original position.
1592 Maybe
<EditorDOMPoint
> caretPoint
;
1593 if (aRangesToDelete
.IsCollapsed() && !aRangesToDelete
.Ranges().IsEmpty()) {
1595 Some(aRangesToDelete
.GetFirstRangeStartPoint
<EditorDOMPoint
>());
1596 if (NS_WARN_IF(!caretPoint
.ref().IsInContentNode())) {
1597 return Err(NS_ERROR_FAILURE
);
1601 Result
<nsIEditor::EDirection
, nsresult
> extendResult
=
1602 aRangesToDelete
.ExtendAnchorFocusRangeFor(aHTMLEditor
,
1603 aDirectionAndAmount
);
1604 if (MOZ_UNLIKELY(extendResult
.isErr())) {
1605 NS_WARNING("AutoRangeArray::ExtendAnchorFocusRangeFor() failed");
1606 return extendResult
.propagateErr();
1608 if (caretPoint
.isSome() &&
1609 MOZ_UNLIKELY(!caretPoint
.ref().IsSetAndValid())) {
1610 NS_WARNING("The caret position became invalid");
1611 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
);
1614 // If there is only one range and it selects an atomic content, we should
1615 // delete it with collapsed range path for making consistent behavior
1616 // between both cases, the content is selected case and caret is at it or
1618 Result
<bool, nsresult
> shrunkenResult
=
1619 aRangesToDelete
.ShrinkRangesIfStartFromOrEndAfterAtomicContent(
1620 aHTMLEditor
, aDirectionAndAmount
,
1621 AutoRangeArray::IfSelectingOnlyOneAtomicContent::Collapse
,
1623 if (MOZ_UNLIKELY(shrunkenResult
.isErr())) {
1625 "AutoRangeArray::ShrinkRangesIfStartFromOrEndAfterAtomicContent() "
1627 return shrunkenResult
.propagateErr();
1630 if (!shrunkenResult
.inspect() || !aRangesToDelete
.IsCollapsed()) {
1631 aDirectionAndAmount
= extendResult
.unwrap();
1634 if (aDirectionAndAmount
== nsIEditor::eNone
) {
1635 MOZ_ASSERT(aRangesToDelete
.Ranges().Length() == 1);
1636 if (!CanFallbackToDeleteRangesWithTransaction(aRangesToDelete
)) {
1637 return EditActionResult::IgnoredResult();
1639 Result
<CaretPoint
, nsresult
> caretPointOrError
=
1640 FallbackToDeleteRangesWithTransaction(aHTMLEditor
, aRangesToDelete
);
1641 if (MOZ_UNLIKELY(caretPointOrError
.isErr())) {
1643 "AutoDeleteRangesHandler::FallbackToDeleteRangesWithTransaction() "
1646 nsresult rv
= caretPointOrError
.inspect().SuggestCaretPointTo(
1647 aHTMLEditor
, {SuggestCaret::OnlyIfHasSuggestion
,
1648 SuggestCaret::OnlyIfTransactionsAllowedToDoIt
,
1649 SuggestCaret::AndIgnoreTrivialError
});
1650 if (NS_FAILED(rv
)) {
1651 NS_WARNING("CaretPoint::SuggestCaretPointTo() failed");
1654 NS_WARNING_ASSERTION(
1655 rv
!= NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR
,
1656 "CaretPoint::SuggestCaretPointTo() failed, but ignored");
1657 // Don't return "ignored" to avoid to fall it back to delete ranges
1659 return EditActionResult::HandledResult();
1662 if (aRangesToDelete
.IsCollapsed()) {
1663 // Use the original caret position for handling the deletion around
1664 // collapsed range because the container may be different from the
1665 // new collapsed position's container.
1666 if (!EditorUtils::IsEditableContent(
1667 *caretPoint
.ref().ContainerAs
<nsIContent
>(), EditorType::HTML
)) {
1668 return EditActionResult::CanceledResult();
1670 WSRunScanner
wsRunScannerAtCaret(
1671 &aEditingHost
, caretPoint
.ref(),
1672 BlockInlineCheck::UseComputedDisplayOutsideStyle
);
1673 WSScanResult scanFromCaretPointResult
=
1674 aDirectionAndAmount
== nsIEditor::eNext
1675 ? wsRunScannerAtCaret
.ScanNextVisibleNodeOrBlockBoundaryFrom(
1677 : wsRunScannerAtCaret
.ScanPreviousVisibleNodeOrBlockBoundaryFrom(
1679 if (MOZ_UNLIKELY(scanFromCaretPointResult
.Failed())) {
1681 "WSRunScanner::Scan(Next|Previous)VisibleNodeOrBlockBoundaryFrom() "
1683 return Err(NS_ERROR_FAILURE
);
1685 if (!scanFromCaretPointResult
.GetContent()) {
1686 return EditActionResult::CanceledResult();
1688 // Short circuit for invisible breaks. delete them and recurse.
1689 if (scanFromCaretPointResult
.ReachedBRElement()) {
1690 if (scanFromCaretPointResult
.BRElementPtr() == &aEditingHost
) {
1691 return EditActionResult::HandledResult();
1693 if (!EditorUtils::IsEditableContent(
1694 *scanFromCaretPointResult
.BRElementPtr(), EditorType::HTML
)) {
1695 return EditActionResult::CanceledResult();
1697 if (HTMLEditUtils::IsInvisibleBRElement(
1698 *scanFromCaretPointResult
.BRElementPtr())) {
1699 // TODO: We should extend the range to delete again before/after
1700 // the caret point and use `HandleDeleteNonCollapsedRanges()`
1701 // instead after we would create delete range computation
1702 // method at switching to the new white-space normalizer.
1703 Result
<CaretPoint
, nsresult
> caretPointOrError
=
1704 WhiteSpaceVisibilityKeeper::
1705 DeleteContentNodeAndJoinTextNodesAroundIt(
1707 MOZ_KnownLive(*scanFromCaretPointResult
.BRElementPtr()),
1708 caretPoint
.ref(), aEditingHost
);
1709 if (MOZ_UNLIKELY(caretPointOrError
.isErr())) {
1711 "WhiteSpaceVisibilityKeeper::"
1712 "DeleteContentNodeAndJoinTextNodesAroundIt() failed");
1713 return caretPointOrError
.propagateErr();
1715 if (caretPointOrError
.inspect().HasCaretPointSuggestion()) {
1716 caretPoint
= Some(caretPointOrError
.unwrap().UnwrapCaretPoint());
1718 if (NS_WARN_IF(!caretPoint
->IsSetAndValid())) {
1719 return Err(NS_ERROR_FAILURE
);
1721 AutoRangeArray
rangesToDelete(caretPoint
.ref());
1722 if (aHTMLEditor
.MayHaveMutationEventListeners(
1723 NS_EVENT_BITS_MUTATION_SUBTREEMODIFIED
|
1724 NS_EVENT_BITS_MUTATION_NODEREMOVED
|
1725 NS_EVENT_BITS_MUTATION_NODEREMOVEDFROMDOCUMENT
)) {
1726 // Let's check whether there is new invisible `<br>` element
1727 // for avoiding infinite recursive calls.
1728 WSRunScanner
wsRunScannerAtCaret(
1729 &aEditingHost
, caretPoint
.ref(),
1730 BlockInlineCheck::UseComputedDisplayOutsideStyle
);
1731 WSScanResult scanFromCaretPointResult
=
1732 aDirectionAndAmount
== nsIEditor::eNext
1733 ? wsRunScannerAtCaret
1734 .ScanNextVisibleNodeOrBlockBoundaryFrom(
1736 : wsRunScannerAtCaret
1737 .ScanPreviousVisibleNodeOrBlockBoundaryFrom(
1739 if (MOZ_UNLIKELY(scanFromCaretPointResult
.Failed())) {
1741 "WSRunScanner::Scan(Next|Previous)"
1742 "VisibleNodeOrBlockBoundaryFrom() failed");
1743 return Err(NS_ERROR_FAILURE
);
1746 scanFromCaretPointResult
.ReachedInvisibleBRElement())) {
1747 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
);
1750 AutoDeleteRangesHandler
anotherHandler(this);
1751 Result
<EditActionResult
, nsresult
> result
=
1752 anotherHandler
.Run(aHTMLEditor
, aDirectionAndAmount
,
1753 aStripWrappers
, rangesToDelete
, aEditingHost
);
1754 NS_WARNING_ASSERTION(
1755 result
.isOk(), "Recursive AutoDeleteRangesHandler::Run() failed");
1760 Result
<EditActionResult
, nsresult
> result
=
1761 HandleDeleteAroundCollapsedRanges(
1762 aHTMLEditor
, aDirectionAndAmount
, aStripWrappers
, aRangesToDelete
,
1763 wsRunScannerAtCaret
, scanFromCaretPointResult
, aEditingHost
);
1764 NS_WARNING_ASSERTION(result
.isOk(),
1765 "AutoDeleteRangesHandler::"
1766 "HandleDeleteAroundCollapsedRanges() failed");
1771 Result
<EditActionResult
, nsresult
> result
= HandleDeleteNonCollapsedRanges(
1772 aHTMLEditor
, aDirectionAndAmount
, aStripWrappers
, aRangesToDelete
,
1773 selectionWasCollapsed
, aEditingHost
);
1774 NS_WARNING_ASSERTION(
1776 "AutoDeleteRangesHandler::HandleDeleteNonCollapsedRanges() failed");
1781 HTMLEditor::AutoDeleteRangesHandler::ComputeRangesToDeleteAroundCollapsedRanges(
1782 const HTMLEditor
& aHTMLEditor
, nsIEditor::EDirection aDirectionAndAmount
,
1783 AutoRangeArray
& aRangesToDelete
, const WSRunScanner
& aWSRunScannerAtCaret
,
1784 const WSScanResult
& aScanFromCaretPointResult
,
1785 const Element
& aEditingHost
) const {
1786 if (aScanFromCaretPointResult
.InCollapsibleWhiteSpaces() ||
1787 aScanFromCaretPointResult
.InNonCollapsibleCharacters() ||
1788 aScanFromCaretPointResult
.ReachedPreformattedLineBreak()) {
1789 nsresult rv
= aRangesToDelete
.Collapse(
1790 aScanFromCaretPointResult
.Point
<EditorRawDOMPoint
>());
1791 if (MOZ_UNLIKELY(NS_FAILED(rv
))) {
1792 NS_WARNING("AutoRangeArray::Collapse() failed");
1793 return NS_ERROR_FAILURE
;
1795 rv
= ComputeRangesToDeleteTextAroundCollapsedRanges(
1796 aDirectionAndAmount
, aRangesToDelete
, aEditingHost
);
1797 NS_WARNING_ASSERTION(
1799 "AutoDeleteRangesHandler::"
1800 "ComputeRangesToDeleteTextAroundCollapsedRanges() failed");
1804 if (aScanFromCaretPointResult
.ReachedSpecialContent() ||
1805 aScanFromCaretPointResult
.ReachedBRElement() ||
1806 aScanFromCaretPointResult
.ReachedHRElement() ||
1807 aScanFromCaretPointResult
.ReachedNonEditableOtherBlockElement()) {
1808 if (aScanFromCaretPointResult
.GetContent() ==
1809 aWSRunScannerAtCaret
.GetEditingHost()) {
1812 nsIContent
* atomicContent
= GetAtomicContentToDelete(
1813 aDirectionAndAmount
, aWSRunScannerAtCaret
, aScanFromCaretPointResult
);
1814 if (!HTMLEditUtils::IsRemovableNode(*atomicContent
)) {
1816 "AutoDeleteRangesHandler::GetAtomicContentToDelete() cannot find "
1817 "removable atomic content");
1818 return NS_ERROR_FAILURE
;
1820 nsresult rv
= ComputeRangesToDeleteAtomicContent(
1821 aWSRunScannerAtCaret
.GetEditingHost(), *atomicContent
, aRangesToDelete
);
1822 NS_WARNING_ASSERTION(
1824 "AutoDeleteRangesHandler::ComputeRangesToDeleteAtomicContent() failed");
1828 if (aScanFromCaretPointResult
.ReachedOtherBlockElement()) {
1829 if (NS_WARN_IF(!aScanFromCaretPointResult
.GetContent()->IsElement())) {
1830 return NS_ERROR_FAILURE
;
1832 MOZ_ASSERT(!aRangesToDelete
.Ranges().IsEmpty());
1833 bool handled
= false;
1834 for (const OwningNonNull
<nsRange
>& range
: aRangesToDelete
.Ranges()) {
1835 MOZ_ASSERT(range
->IsPositioned());
1836 AutoBlockElementsJoiner
joiner(*this);
1837 if (!joiner
.PrepareToDeleteAtOtherBlockBoundary(
1838 aHTMLEditor
, aDirectionAndAmount
,
1839 *aScanFromCaretPointResult
.ElementPtr(),
1840 aWSRunScannerAtCaret
.ScanStartRef(), aWSRunScannerAtCaret
)) {
1844 nsresult rv
= joiner
.ComputeRangeToDelete(
1845 aHTMLEditor
, aDirectionAndAmount
, aWSRunScannerAtCaret
.ScanStartRef(),
1846 range
, aEditingHost
);
1847 if (NS_FAILED(rv
)) {
1849 "AutoBlockElementsJoiner::ComputeRangeToDelete() failed (other "
1854 return handled
? NS_OK
: NS_SUCCESS_DOM_NO_OPERATION
;
1857 if (aScanFromCaretPointResult
.ReachedCurrentBlockBoundary()) {
1858 if (NS_WARN_IF(!aScanFromCaretPointResult
.GetContent()->IsElement())) {
1859 return NS_ERROR_FAILURE
;
1861 MOZ_ASSERT(!aRangesToDelete
.Ranges().IsEmpty());
1862 bool handled
= false;
1863 for (const OwningNonNull
<nsRange
>& range
: aRangesToDelete
.Ranges()) {
1864 AutoBlockElementsJoiner
joiner(*this);
1865 if (!joiner
.PrepareToDeleteAtCurrentBlockBoundary(
1866 aHTMLEditor
, aDirectionAndAmount
,
1867 *aScanFromCaretPointResult
.ElementPtr(),
1868 aWSRunScannerAtCaret
.ScanStartRef())) {
1872 nsresult rv
= joiner
.ComputeRangeToDelete(
1873 aHTMLEditor
, aDirectionAndAmount
, aWSRunScannerAtCaret
.ScanStartRef(),
1874 range
, aEditingHost
);
1875 if (NS_FAILED(rv
)) {
1877 "AutoBlockElementsJoiner::ComputeRangeToDelete() failed (current "
1882 return handled
? NS_OK
: NS_SUCCESS_DOM_NO_OPERATION
;
1888 Result
<EditActionResult
, nsresult
>
1889 HTMLEditor::AutoDeleteRangesHandler::HandleDeleteAroundCollapsedRanges(
1890 HTMLEditor
& aHTMLEditor
, nsIEditor::EDirection aDirectionAndAmount
,
1891 nsIEditor::EStripWrappers aStripWrappers
, AutoRangeArray
& aRangesToDelete
,
1892 const WSRunScanner
& aWSRunScannerAtCaret
,
1893 const WSScanResult
& aScanFromCaretPointResult
,
1894 const Element
& aEditingHost
) {
1895 MOZ_ASSERT(aHTMLEditor
.IsTopLevelEditSubActionDataAvailable());
1896 MOZ_ASSERT(aRangesToDelete
.IsCollapsed());
1897 MOZ_ASSERT(aDirectionAndAmount
!= nsIEditor::eNone
);
1898 MOZ_ASSERT(aWSRunScannerAtCaret
.ScanStartRef().IsInContentNode());
1899 MOZ_ASSERT(EditorUtils::IsEditableContent(
1900 *aWSRunScannerAtCaret
.ScanStartRef().ContainerAs
<nsIContent
>(),
1903 if (StaticPrefs::editor_white_space_normalization_blink_compatible()) {
1904 if (aScanFromCaretPointResult
.InCollapsibleWhiteSpaces() ||
1905 aScanFromCaretPointResult
.InNonCollapsibleCharacters() ||
1906 aScanFromCaretPointResult
.ReachedPreformattedLineBreak()) {
1907 nsresult rv
= aRangesToDelete
.Collapse(
1908 aScanFromCaretPointResult
.Point
<EditorRawDOMPoint
>());
1909 if (NS_FAILED(rv
)) {
1910 NS_WARNING("AutoRangeArray::Collapse() failed");
1911 return Err(NS_ERROR_FAILURE
);
1913 Result
<CaretPoint
, nsresult
> caretPointOrError
=
1914 HandleDeleteTextAroundCollapsedRanges(
1915 aHTMLEditor
, aDirectionAndAmount
, aRangesToDelete
, aEditingHost
);
1916 if (MOZ_UNLIKELY(caretPointOrError
.isErr())) {
1918 "AutoDeleteRangesHandler::HandleDeleteTextAroundCollapsedRanges() "
1920 return caretPointOrError
.propagateErr();
1922 rv
= caretPointOrError
.unwrap().SuggestCaretPointTo(
1923 aHTMLEditor
, {SuggestCaret::OnlyIfHasSuggestion
,
1924 SuggestCaret::OnlyIfTransactionsAllowedToDoIt
,
1925 SuggestCaret::AndIgnoreTrivialError
});
1926 if (NS_FAILED(rv
)) {
1927 NS_WARNING("CaretPoint::SuggestCaretPointTo() failed");
1930 NS_WARNING_ASSERTION(
1931 rv
!= NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR
,
1932 "CaretPoint::SuggestCaretPoint() failed, but ignored");
1933 return EditActionResult::HandledResult();
1937 if (aScanFromCaretPointResult
.InCollapsibleWhiteSpaces() ||
1938 aScanFromCaretPointResult
.ReachedPreformattedLineBreak()) {
1939 Result
<CaretPoint
, nsresult
> caretPointOrError
=
1940 HandleDeleteCollapsedSelectionAtWhiteSpaces(
1941 aHTMLEditor
, aDirectionAndAmount
,
1942 aWSRunScannerAtCaret
.ScanStartRef(), aEditingHost
);
1943 if (MOZ_UNLIKELY(caretPointOrError
.isErr())) {
1945 "AutoDeleteRangesHandler::"
1946 "HandleDeleteCollapsedSelectionAtWhiteSpaces() failed");
1947 return caretPointOrError
.propagateErr();
1949 nsresult rv
= caretPointOrError
.unwrap().SuggestCaretPointTo(
1950 aHTMLEditor
, {SuggestCaret::OnlyIfHasSuggestion
});
1951 if (NS_FAILED(rv
)) {
1952 NS_WARNING("CaretPoint::SuggestCaretPointTo() failed");
1955 NS_WARNING_ASSERTION(
1956 rv
!= NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR
,
1957 "CaretPoint::SuggestCaretPointTo() failed, but ignored");
1958 return EditActionResult::HandledResult();
1961 if (aScanFromCaretPointResult
.InNonCollapsibleCharacters()) {
1962 if (NS_WARN_IF(!aScanFromCaretPointResult
.GetContent()->IsText())) {
1963 return Err(NS_ERROR_FAILURE
);
1965 Result
<CaretPoint
, nsresult
> caretPointOrError
=
1966 HandleDeleteCollapsedSelectionAtVisibleChar(
1967 aHTMLEditor
, aDirectionAndAmount
, aRangesToDelete
,
1968 aScanFromCaretPointResult
.Point
<EditorDOMPoint
>(), aEditingHost
);
1969 if (MOZ_UNLIKELY(caretPointOrError
.isErr())) {
1971 "AutoDeleteRangesHandler::"
1972 "HandleDeleteCollapsedSelectionAtVisibleChar() failed");
1973 return caretPointOrError
.propagateErr();
1975 nsresult rv
= caretPointOrError
.unwrap().SuggestCaretPointTo(
1976 aHTMLEditor
, {SuggestCaret::OnlyIfHasSuggestion
});
1977 if (NS_FAILED(rv
)) {
1978 NS_WARNING("CaretPoint::SuggestCaretPointTo() failed");
1981 NS_WARNING_ASSERTION(
1982 rv
!= NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR
,
1983 "CaretPoint::SuggestCaretPointTo() failed, but ignored");
1984 return EditActionResult::HandledResult();
1987 if (aScanFromCaretPointResult
.ReachedSpecialContent() ||
1988 aScanFromCaretPointResult
.ReachedBRElement() ||
1989 aScanFromCaretPointResult
.ReachedHRElement() ||
1990 aScanFromCaretPointResult
.ReachedNonEditableOtherBlockElement()) {
1991 if (aScanFromCaretPointResult
.GetContent() == &aEditingHost
) {
1992 return EditActionResult::HandledResult();
1994 nsCOMPtr
<nsIContent
> atomicContent
= GetAtomicContentToDelete(
1995 aDirectionAndAmount
, aWSRunScannerAtCaret
, aScanFromCaretPointResult
);
1996 if (MOZ_UNLIKELY(!HTMLEditUtils::IsRemovableNode(*atomicContent
))) {
1998 "AutoDeleteRangesHandler::GetAtomicContentToDelete() cannot find "
1999 "removable atomic content");
2000 return Err(NS_ERROR_FAILURE
);
2002 Result
<CaretPoint
, nsresult
> caretPointOrError
= HandleDeleteAtomicContent(
2003 aHTMLEditor
, *atomicContent
, aWSRunScannerAtCaret
.ScanStartRef(),
2004 aWSRunScannerAtCaret
, aEditingHost
);
2005 if (MOZ_UNLIKELY(caretPointOrError
.isErr())) {
2006 NS_WARNING("AutoDeleteRangesHandler::HandleDeleteAtomicContent() failed");
2007 return caretPointOrError
.propagateErr();
2009 nsresult rv
= caretPointOrError
.unwrap().SuggestCaretPointTo(
2010 aHTMLEditor
, {SuggestCaret::OnlyIfHasSuggestion
});
2011 if (NS_FAILED(rv
)) {
2012 NS_WARNING("CaretPoint::SuggestCaretPointTo() failed");
2015 NS_WARNING_ASSERTION(
2016 rv
!= NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR
,
2017 "CaretPoint::SuggestCaretPointTo() failed, but ignored");
2018 return EditActionResult::HandledResult();
2021 if (aScanFromCaretPointResult
.ReachedOtherBlockElement()) {
2022 if (NS_WARN_IF(!aScanFromCaretPointResult
.GetContent()->IsElement())) {
2023 return Err(NS_ERROR_FAILURE
);
2025 MOZ_ASSERT(!aRangesToDelete
.Ranges().IsEmpty());
2026 bool allRangesNotHandled
= true;
2027 auto ret
= EditActionResult::IgnoredResult();
2028 for (const OwningNonNull
<nsRange
>& range
: aRangesToDelete
.Ranges()) {
2029 AutoBlockElementsJoiner
joiner(*this);
2030 if (!joiner
.PrepareToDeleteAtOtherBlockBoundary(
2031 aHTMLEditor
, aDirectionAndAmount
,
2032 *aScanFromCaretPointResult
.ElementPtr(),
2033 aWSRunScannerAtCaret
.ScanStartRef(), aWSRunScannerAtCaret
)) {
2036 allRangesNotHandled
= false;
2037 Result
<EditActionResult
, nsresult
> result
=
2038 joiner
.Run(aHTMLEditor
, aDirectionAndAmount
, aStripWrappers
,
2039 aWSRunScannerAtCaret
.ScanStartRef(), MOZ_KnownLive(range
),
2041 if (MOZ_UNLIKELY(result
.isErr())) {
2043 "AutoBlockElementsJoiner::Run() failed (other block boundary)");
2046 ret
|= result
.inspect();
2048 return allRangesNotHandled
? EditActionResult::CanceledResult()
2052 if (aScanFromCaretPointResult
.ReachedCurrentBlockBoundary()) {
2053 if (NS_WARN_IF(!aScanFromCaretPointResult
.GetContent()->IsElement())) {
2054 return Err(NS_ERROR_FAILURE
);
2056 MOZ_ASSERT(!aRangesToDelete
.Ranges().IsEmpty());
2057 bool allRangesNotHandled
= true;
2058 auto ret
= EditActionResult::IgnoredResult();
2059 for (const OwningNonNull
<nsRange
>& range
: aRangesToDelete
.Ranges()) {
2060 AutoBlockElementsJoiner
joiner(*this);
2061 if (!joiner
.PrepareToDeleteAtCurrentBlockBoundary(
2062 aHTMLEditor
, aDirectionAndAmount
,
2063 *aScanFromCaretPointResult
.ElementPtr(),
2064 aWSRunScannerAtCaret
.ScanStartRef())) {
2067 allRangesNotHandled
= false;
2068 Result
<EditActionResult
, nsresult
> result
=
2069 joiner
.Run(aHTMLEditor
, aDirectionAndAmount
, aStripWrappers
,
2070 aWSRunScannerAtCaret
.ScanStartRef(), MOZ_KnownLive(range
),
2072 if (MOZ_UNLIKELY(result
.isErr())) {
2074 "AutoBlockElementsJoiner::Run() failed (current block boundary)");
2077 ret
|= result
.inspect();
2079 return allRangesNotHandled
? EditActionResult::CanceledResult()
2083 MOZ_ASSERT_UNREACHABLE("New type of reached content hasn't been handled yet");
2084 return EditActionResult::IgnoredResult();
2087 nsresult
HTMLEditor::AutoDeleteRangesHandler::
2088 ComputeRangesToDeleteTextAroundCollapsedRanges(
2089 nsIEditor::EDirection aDirectionAndAmount
,
2090 AutoRangeArray
& aRangesToDelete
, const Element
& aEditingHost
) const {
2091 MOZ_ASSERT(aDirectionAndAmount
== nsIEditor::eNext
||
2092 aDirectionAndAmount
== nsIEditor::ePrevious
);
2094 const auto caretPosition
=
2095 aRangesToDelete
.GetFirstRangeStartPoint
<EditorDOMPoint
>();
2096 MOZ_ASSERT(caretPosition
.IsSetAndValid());
2097 if (MOZ_UNLIKELY(NS_WARN_IF(!caretPosition
.IsInContentNode()))) {
2098 return NS_ERROR_FAILURE
;
2101 EditorDOMRangeInTexts rangeToDelete
;
2102 if (aDirectionAndAmount
== nsIEditor::eNext
) {
2103 Result
<EditorDOMRangeInTexts
, nsresult
> result
=
2104 WSRunScanner::GetRangeInTextNodesToForwardDeleteFrom(caretPosition
,
2106 if (result
.isErr()) {
2108 "WSRunScanner::GetRangeInTextNodesToForwardDeleteFrom() failed");
2109 return result
.unwrapErr();
2111 rangeToDelete
= result
.unwrap();
2112 if (!rangeToDelete
.IsPositioned()) {
2113 return NS_OK
; // no range to delete, but consume it.
2116 Result
<EditorDOMRangeInTexts
, nsresult
> result
=
2117 WSRunScanner::GetRangeInTextNodesToBackspaceFrom(caretPosition
,
2119 if (result
.isErr()) {
2120 NS_WARNING("WSRunScanner::GetRangeInTextNodesToBackspaceFrom() failed");
2121 return result
.unwrapErr();
2123 rangeToDelete
= result
.unwrap();
2124 if (!rangeToDelete
.IsPositioned()) {
2125 return NS_OK
; // no range to delete, but consume it.
2129 nsresult rv
= aRangesToDelete
.SetStartAndEnd(rangeToDelete
.StartRef(),
2130 rangeToDelete
.EndRef());
2131 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
2132 "AutoArrayRanges::SetStartAndEnd() failed");
2136 Result
<CaretPoint
, nsresult
>
2137 HTMLEditor::AutoDeleteRangesHandler::HandleDeleteTextAroundCollapsedRanges(
2138 HTMLEditor
& aHTMLEditor
, nsIEditor::EDirection aDirectionAndAmount
,
2139 AutoRangeArray
& aRangesToDelete
, const Element
& aEditingHost
) {
2140 MOZ_ASSERT(aHTMLEditor
.IsEditActionDataAvailable());
2141 MOZ_ASSERT(aDirectionAndAmount
== nsIEditor::eNext
||
2142 aDirectionAndAmount
== nsIEditor::ePrevious
);
2144 nsresult rv
= ComputeRangesToDeleteTextAroundCollapsedRanges(
2145 aDirectionAndAmount
, aRangesToDelete
, aEditingHost
);
2146 if (NS_FAILED(rv
)) {
2147 return Err(NS_ERROR_FAILURE
);
2149 if (MOZ_UNLIKELY(aRangesToDelete
.IsCollapsed())) {
2150 return CaretPoint(EditorDOMPoint()); // no range to delete
2153 // FYI: rangeToDelete does not contain newly empty inline ancestors which
2154 // are removed by DeleteTextAndNormalizeSurroundingWhiteSpaces().
2155 // So, if `getTargetRanges()` needs to include parent empty elements,
2156 // we need to extend the range with
2157 // HTMLEditUtils::GetMostDistantAncestorEditableEmptyInlineElement().
2158 EditorRawDOMRange
rangeToDelete(aRangesToDelete
.FirstRangeRef());
2159 if (MOZ_UNLIKELY(!rangeToDelete
.IsInTextNodes())) {
2160 NS_WARNING("The extended range to delete character was not in text nodes");
2161 return Err(NS_ERROR_FAILURE
);
2164 Result
<CaretPoint
, nsresult
> caretPointOrError
=
2165 aHTMLEditor
.DeleteTextAndNormalizeSurroundingWhiteSpaces(
2166 rangeToDelete
.StartRef().AsInText(),
2167 rangeToDelete
.EndRef().AsInText(),
2168 TreatEmptyTextNodes::RemoveAllEmptyInlineAncestors
,
2169 aDirectionAndAmount
== nsIEditor::eNext
? DeleteDirection::Forward
2170 : DeleteDirection::Backward
);
2171 aHTMLEditor
.TopLevelEditSubActionDataRef().mDidNormalizeWhitespaces
= true;
2172 NS_WARNING_ASSERTION(
2173 caretPointOrError
.isOk(),
2174 "HTMLEditor::DeleteTextAndNormalizeSurroundingWhiteSpaces() failed");
2175 return caretPointOrError
;
2178 Result
<CaretPoint
, nsresult
> HTMLEditor::AutoDeleteRangesHandler::
2179 HandleDeleteCollapsedSelectionAtWhiteSpaces(
2180 HTMLEditor
& aHTMLEditor
, nsIEditor::EDirection aDirectionAndAmount
,
2181 const EditorDOMPoint
& aPointToDelete
, const Element
& aEditingHost
) {
2182 MOZ_ASSERT(aHTMLEditor
.IsEditActionDataAvailable());
2183 MOZ_ASSERT(!StaticPrefs::editor_white_space_normalization_blink_compatible());
2185 EditorDOMPoint pointToPutCaret
;
2186 if (aDirectionAndAmount
== nsIEditor::eNext
) {
2187 Result
<CaretPoint
, nsresult
> caretPointOrError
=
2188 WhiteSpaceVisibilityKeeper::DeleteInclusiveNextWhiteSpace(
2189 aHTMLEditor
, aPointToDelete
, aEditingHost
);
2190 if (MOZ_UNLIKELY(caretPointOrError
.isErr())) {
2192 "WhiteSpaceVisibilityKeeper::DeleteInclusiveNextWhiteSpace() failed");
2193 return caretPointOrError
;
2195 caretPointOrError
.unwrap().MoveCaretPointTo(
2196 pointToPutCaret
, aHTMLEditor
,
2197 {SuggestCaret::OnlyIfHasSuggestion
,
2198 SuggestCaret::OnlyIfTransactionsAllowedToDoIt
});
2200 Result
<CaretPoint
, nsresult
> caretPointOrError
=
2201 WhiteSpaceVisibilityKeeper::DeletePreviousWhiteSpace(
2202 aHTMLEditor
, aPointToDelete
, aEditingHost
);
2203 if (MOZ_UNLIKELY(caretPointOrError
.isErr())) {
2205 "WhiteSpaceVisibilityKeeper::DeletePreviousWhiteSpace() failed");
2206 return caretPointOrError
;
2208 caretPointOrError
.unwrap().MoveCaretPointTo(
2209 pointToPutCaret
, aHTMLEditor
,
2210 {SuggestCaret::OnlyIfHasSuggestion
,
2211 SuggestCaret::OnlyIfTransactionsAllowedToDoIt
});
2213 const auto newCaretPosition
=
2214 aHTMLEditor
.GetFirstSelectionStartPoint
<EditorDOMPoint
>();
2215 if (MOZ_UNLIKELY(!newCaretPosition
.IsSet())) {
2216 NS_WARNING("There was no selection range");
2217 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
);
2219 Result
<CaretPoint
, nsresult
> caretPointOrError
=
2220 aHTMLEditor
.InsertBRElementIfHardLineIsEmptyAndEndsWithBlockBoundary(
2222 if (MOZ_UNLIKELY(caretPointOrError
.isErr())) {
2224 "HTMLEditor::InsertBRElementIfHardLineIsEmptyAndEndsWithBlockBoundary()"
2226 return caretPointOrError
;
2228 caretPointOrError
.unwrap().MoveCaretPointTo(
2229 pointToPutCaret
, {SuggestCaret::OnlyIfHasSuggestion
});
2230 return CaretPoint(std::move(pointToPutCaret
));
2233 Result
<CaretPoint
, nsresult
> HTMLEditor::AutoDeleteRangesHandler::
2234 HandleDeleteCollapsedSelectionAtVisibleChar(
2235 HTMLEditor
& aHTMLEditor
, nsIEditor::EDirection aDirectionAndAmount
,
2236 AutoRangeArray
& aRangesToDelete
,
2237 const EditorDOMPoint
& aPointAtDeletingChar
,
2238 const Element
& aEditingHost
) {
2239 MOZ_ASSERT(aHTMLEditor
.IsTopLevelEditSubActionDataAvailable());
2240 MOZ_ASSERT(!StaticPrefs::editor_white_space_normalization_blink_compatible());
2241 MOZ_ASSERT(aPointAtDeletingChar
.IsSet());
2242 MOZ_ASSERT(aPointAtDeletingChar
.IsInTextNode());
2244 OwningNonNull
<Text
> visibleTextNode
=
2245 *aPointAtDeletingChar
.ContainerAs
<Text
>();
2246 EditorDOMPoint startToDelete
, endToDelete
;
2247 // FIXME: This does not care grapheme cluster of complicate character
2248 // sequence like Emoji.
2249 // TODO: Investigate what happens if a grapheme cluster which should be
2250 // delete once is split to multiple text nodes.
2251 // TODO: We should stop using this path, instead, we should extend the range
2252 // before calling this method.
2253 if (aDirectionAndAmount
== nsIEditor::ePrevious
) {
2254 if (MOZ_UNLIKELY(aPointAtDeletingChar
.IsStartOfContainer())) {
2255 return Err(NS_ERROR_UNEXPECTED
);
2257 startToDelete
= aPointAtDeletingChar
.PreviousPoint();
2258 endToDelete
= aPointAtDeletingChar
;
2259 // Bug 1068979: delete both codepoints if surrogate pair
2260 if (!startToDelete
.IsStartOfContainer()) {
2261 const nsTextFragment
* text
= &visibleTextNode
->TextFragment();
2262 if (text
->IsLowSurrogateFollowingHighSurrogateAt(
2263 startToDelete
.Offset())) {
2264 startToDelete
.RewindOffset();
2268 if (NS_WARN_IF(aRangesToDelete
.Ranges().IsEmpty()) ||
2269 NS_WARN_IF(aRangesToDelete
.FirstRangeRef()->GetStartContainer() !=
2270 aPointAtDeletingChar
.GetContainer()) ||
2271 NS_WARN_IF(aRangesToDelete
.FirstRangeRef()->GetEndContainer() !=
2272 aPointAtDeletingChar
.GetContainer())) {
2273 return Err(NS_ERROR_FAILURE
);
2275 startToDelete
= aRangesToDelete
.FirstRangeRef()->StartRef();
2276 endToDelete
= aRangesToDelete
.FirstRangeRef()->EndRef();
2280 Result
<CaretPoint
, nsresult
> caretPointOrError
=
2281 WhiteSpaceVisibilityKeeper::PrepareToDeleteRangeAndTrackPoints(
2282 aHTMLEditor
, &startToDelete
, &endToDelete
, aEditingHost
);
2283 if (MOZ_UNLIKELY(caretPointOrError
.isErr())) {
2285 "WhiteSpaceVisibilityKeeper::PrepareToDeleteRangeAndTrackPoints() "
2287 return caretPointOrError
.propagateErr();
2289 // Ignore caret position because we'll set caret position below
2290 caretPointOrError
.unwrap().IgnoreCaretPointSuggestion();
2293 if (aHTMLEditor
.MayHaveMutationEventListeners(
2294 NS_EVENT_BITS_MUTATION_NODEREMOVED
|
2295 NS_EVENT_BITS_MUTATION_NODEREMOVEDFROMDOCUMENT
|
2296 NS_EVENT_BITS_MUTATION_ATTRMODIFIED
|
2297 NS_EVENT_BITS_MUTATION_CHARACTERDATAMODIFIED
) &&
2298 (NS_WARN_IF(!startToDelete
.IsSetAndValid()) ||
2299 NS_WARN_IF(!startToDelete
.IsInTextNode()) ||
2300 NS_WARN_IF(!endToDelete
.IsSetAndValid()) ||
2301 NS_WARN_IF(!endToDelete
.IsInTextNode()) ||
2302 NS_WARN_IF(startToDelete
.ContainerAs
<Text
>() != visibleTextNode
) ||
2303 NS_WARN_IF(endToDelete
.ContainerAs
<Text
>() != visibleTextNode
) ||
2304 NS_WARN_IF(startToDelete
.Offset() >= endToDelete
.Offset()))) {
2305 NS_WARNING("Mutation event listener changed the DOM tree");
2306 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
);
2309 EditorDOMPoint pointToPutCaret
= startToDelete
;
2311 AutoTrackDOMPoint
trackPointToPutCaret(aHTMLEditor
.RangeUpdaterRef(),
2313 Result
<CaretPoint
, nsresult
> caretPointOrError
=
2314 aHTMLEditor
.DeleteTextWithTransaction(
2315 visibleTextNode
, startToDelete
.Offset(),
2316 endToDelete
.Offset() - startToDelete
.Offset());
2317 if (MOZ_UNLIKELY(caretPointOrError
.isErr())) {
2318 NS_WARNING("HTMLEditor::DeleteTextWithTransaction() failed");
2319 return caretPointOrError
.propagateErr();
2321 trackPointToPutCaret
.FlushAndStopTracking();
2322 caretPointOrError
.unwrap().MoveCaretPointTo(
2323 pointToPutCaret
, aHTMLEditor
,
2324 {SuggestCaret::OnlyIfHasSuggestion
,
2325 SuggestCaret::OnlyIfTransactionsAllowedToDoIt
});
2328 // XXX When Backspace key is pressed, Chromium removes following empty
2329 // text nodes when removing the last character of the non-empty text
2330 // node. However, Edge never removes empty text nodes even if
2331 // selection is in the following empty text node(s). For now, we
2332 // should keep our traditional behavior same as Edge for backward
2334 // XXX When Delete key is pressed, Edge removes all preceding empty
2335 // text nodes when removing the first character of the non-empty
2336 // text node. Chromium removes only selected empty text node and
2337 // following empty text nodes and the first character of the
2338 // non-empty text node. For now, we should keep our traditional
2339 // behavior same as Chromium for backward compatibility.
2341 AutoTrackDOMPoint
trackPointToPutCaret(aHTMLEditor
.RangeUpdaterRef(),
2344 DeleteNodeIfInvisibleAndEditableTextNode(aHTMLEditor
, visibleTextNode
);
2345 if (NS_WARN_IF(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
2346 return Err(NS_ERROR_EDITOR_DESTROYED
);
2348 NS_WARNING_ASSERTION(
2350 "AutoDeleteRangesHandler::DeleteNodeIfInvisibleAndEditableTextNode() "
2351 "failed, but ignored");
2354 if (NS_WARN_IF(!pointToPutCaret
.IsSet())) {
2355 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
);
2358 // XXX `Selection` may be modified by mutation event listeners so
2359 // that we should use EditorDOMPoint::AtEndOf(visibleTextNode)
2360 // instead. (Perhaps, we don't and/or shouldn't need to do this
2361 // if the text node is preformatted.)
2362 AutoTrackDOMPoint
trackPointToPutCaret(aHTMLEditor
.RangeUpdaterRef(),
2364 Result
<CaretPoint
, nsresult
> caretPointOrError
=
2365 aHTMLEditor
.InsertBRElementIfHardLineIsEmptyAndEndsWithBlockBoundary(
2367 if (MOZ_UNLIKELY(caretPointOrError
.isErr())) {
2369 "HTMLEditor::InsertBRElementIfHardLineIsEmptyAndEndsWithBlockBoundary()"
2371 return caretPointOrError
.propagateErr();
2373 trackPointToPutCaret
.FlushAndStopTracking();
2374 caretPointOrError
.unwrap().MoveCaretPointTo(
2375 pointToPutCaret
, {SuggestCaret::OnlyIfHasSuggestion
});
2376 // Remember that we did a ranged delete for the benefit of
2377 // AfterEditInner().
2378 aHTMLEditor
.TopLevelEditSubActionDataRef().mDidDeleteNonCollapsedRange
= true;
2379 return CaretPoint(std::move(pointToPutCaret
));
2383 nsIContent
* HTMLEditor::AutoDeleteRangesHandler::GetAtomicContentToDelete(
2384 nsIEditor::EDirection aDirectionAndAmount
,
2385 const WSRunScanner
& aWSRunScannerAtCaret
,
2386 const WSScanResult
& aScanFromCaretPointResult
) {
2387 MOZ_ASSERT(aScanFromCaretPointResult
.GetContent());
2389 if (!aScanFromCaretPointResult
.ReachedSpecialContent()) {
2390 return aScanFromCaretPointResult
.GetContent();
2393 if (!aScanFromCaretPointResult
.GetContent()->IsText() ||
2394 HTMLEditUtils::IsRemovableNode(*aScanFromCaretPointResult
.GetContent())) {
2395 return aScanFromCaretPointResult
.GetContent();
2398 // aScanFromCaretPointResult is non-removable text node.
2399 // Since we try removing atomic content, we look for removable node from
2400 // scanned point that is non-removable text.
2401 nsIContent
* removableRoot
= aScanFromCaretPointResult
.GetContent();
2402 while (removableRoot
&& !HTMLEditUtils::IsRemovableNode(*removableRoot
)) {
2403 removableRoot
= removableRoot
->GetParent();
2406 if (removableRoot
) {
2407 return removableRoot
;
2410 // Not found better content. This content may not be removable.
2411 return aScanFromCaretPointResult
.GetContent();
2415 HTMLEditor::AutoDeleteRangesHandler::ComputeRangesToDeleteAtomicContent(
2416 Element
* aEditingHost
, const nsIContent
& aAtomicContent
,
2417 AutoRangeArray
& aRangesToDelete
) const {
2418 EditorDOMRange rangeToDelete
=
2419 WSRunScanner::GetRangesForDeletingAtomicContent(aEditingHost
,
2421 if (!rangeToDelete
.IsPositioned()) {
2422 NS_WARNING("WSRunScanner::GetRangeForDeleteAContentNode() failed");
2423 return NS_ERROR_FAILURE
;
2425 nsresult rv
= aRangesToDelete
.SetStartAndEnd(rangeToDelete
.StartRef(),
2426 rangeToDelete
.EndRef());
2427 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
2428 "AutoRangeArray::SetStartAndEnd() failed");
2432 Result
<CaretPoint
, nsresult
>
2433 HTMLEditor::AutoDeleteRangesHandler::HandleDeleteAtomicContent(
2434 HTMLEditor
& aHTMLEditor
, nsIContent
& aAtomicContent
,
2435 const EditorDOMPoint
& aCaretPoint
, const WSRunScanner
& aWSRunScannerAtCaret
,
2436 const Element
& aEditingHost
) {
2437 MOZ_ASSERT(aHTMLEditor
.IsEditActionDataAvailable());
2438 MOZ_ASSERT(!HTMLEditUtils::IsInvisibleBRElement(aAtomicContent
));
2439 MOZ_ASSERT(&aAtomicContent
!= aWSRunScannerAtCaret
.GetEditingHost());
2441 EditorDOMPoint pointToPutCaret
= aCaretPoint
;
2443 AutoTrackDOMPoint
trackPointToPutCaret(aHTMLEditor
.RangeUpdaterRef(),
2445 Result
<CaretPoint
, nsresult
> caretPointOrError
=
2446 WhiteSpaceVisibilityKeeper::DeleteContentNodeAndJoinTextNodesAroundIt(
2447 aHTMLEditor
, aAtomicContent
, aCaretPoint
, aEditingHost
);
2448 if (MOZ_UNLIKELY(caretPointOrError
.isErr())) {
2450 "WhiteSpaceVisibilityKeeper::"
2451 "DeleteContentNodeAndJoinTextNodesAroundIt() failed");
2452 return caretPointOrError
;
2454 trackPointToPutCaret
.FlushAndStopTracking();
2455 caretPointOrError
.unwrap().MoveCaretPointTo(
2456 pointToPutCaret
, aHTMLEditor
,
2457 {SuggestCaret::OnlyIfHasSuggestion
,
2458 SuggestCaret::OnlyIfTransactionsAllowedToDoIt
});
2459 if (NS_WARN_IF(!pointToPutCaret
.IsSet())) {
2460 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
);
2465 AutoTrackDOMPoint
trackPointToPutCaret(aHTMLEditor
.RangeUpdaterRef(),
2467 Result
<CaretPoint
, nsresult
> caretPointOrError
=
2468 aHTMLEditor
.InsertBRElementIfHardLineIsEmptyAndEndsWithBlockBoundary(
2470 if (MOZ_UNLIKELY(caretPointOrError
.isErr())) {
2473 "InsertBRElementIfHardLineIsEmptyAndEndsWithBlockBoundary()"
2475 return caretPointOrError
;
2477 trackPointToPutCaret
.FlushAndStopTracking();
2478 caretPointOrError
.unwrap().MoveCaretPointTo(
2479 pointToPutCaret
, aHTMLEditor
, {SuggestCaret::OnlyIfHasSuggestion
});
2480 if (NS_WARN_IF(!pointToPutCaret
.IsSet())) {
2481 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
);
2484 return CaretPoint(std::move(pointToPutCaret
));
2487 bool HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
2488 PrepareToDeleteAtOtherBlockBoundary(
2489 const HTMLEditor
& aHTMLEditor
,
2490 nsIEditor::EDirection aDirectionAndAmount
, Element
& aOtherBlockElement
,
2491 const EditorDOMPoint
& aCaretPoint
,
2492 const WSRunScanner
& aWSRunScannerAtCaret
) {
2493 MOZ_ASSERT(aHTMLEditor
.IsEditActionDataAvailable());
2494 MOZ_ASSERT(aCaretPoint
.IsSetAndValid());
2496 mMode
= Mode::JoinOtherBlock
;
2498 // Make sure it's not a table element. If so, cancel the operation
2499 // (translation: users cannot backspace or delete across table cells)
2500 if (HTMLEditUtils::IsAnyTableElement(&aOtherBlockElement
)) {
2504 // First find the adjacent node in the block
2505 if (aDirectionAndAmount
== nsIEditor::ePrevious
) {
2506 mLeafContentInOtherBlock
= HTMLEditUtils::GetLastLeafContent(
2507 aOtherBlockElement
, {LeafNodeType::OnlyEditableLeafNode
},
2508 BlockInlineCheck::Unused
, &aOtherBlockElement
);
2509 mLeftContent
= mLeafContentInOtherBlock
;
2510 mRightContent
= aCaretPoint
.GetContainerAs
<nsIContent
>();
2512 mLeafContentInOtherBlock
= HTMLEditUtils::GetFirstLeafContent(
2513 aOtherBlockElement
, {LeafNodeType::OnlyEditableLeafNode
},
2514 BlockInlineCheck::Unused
, &aOtherBlockElement
);
2515 mLeftContent
= aCaretPoint
.GetContainerAs
<nsIContent
>();
2516 mRightContent
= mLeafContentInOtherBlock
;
2519 // Next to a block. See if we are between the block and a `<br>`.
2520 // If so, we really want to delete the `<br>`. Else join content at
2521 // selection to the block.
2522 WSScanResult scanFromCaretResult
=
2523 aDirectionAndAmount
== nsIEditor::eNext
2524 ? aWSRunScannerAtCaret
.ScanPreviousVisibleNodeOrBlockBoundaryFrom(
2526 : aWSRunScannerAtCaret
.ScanNextVisibleNodeOrBlockBoundaryFrom(
2528 // If we found a `<br>` element, we need to delete it instead of joining the
2530 if (scanFromCaretResult
.ReachedBRElement()) {
2531 mBRElement
= scanFromCaretResult
.BRElementPtr();
2532 mMode
= Mode::DeleteBRElement
;
2536 return mLeftContent
&& mRightContent
;
2539 nsresult
HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
2540 ComputeRangeToDeleteBRElement(nsRange
& aRangeToDelete
) const {
2541 MOZ_ASSERT(mBRElement
);
2542 // XXX Why don't we scan invisible leading white-spaces which follows the
2544 IgnoredErrorResult error
;
2545 aRangeToDelete
.SelectNode(*mBRElement
, error
);
2546 NS_WARNING_ASSERTION(!error
.Failed(), "nsRange::SelectNode() failed");
2547 return error
.StealNSResult();
2550 Result
<EditActionResult
, nsresult
>
2551 HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::DeleteBRElement(
2552 HTMLEditor
& aHTMLEditor
, nsIEditor::EDirection aDirectionAndAmount
,
2553 const Element
& aEditingHost
) {
2554 MOZ_ASSERT(aHTMLEditor
.IsEditActionDataAvailable());
2555 MOZ_ASSERT(mBRElement
);
2557 // If we're deleting selection (not replacing with new content), we should
2558 // put caret to end of preceding text node if there is. Then, users can type
2559 // text in it like the other browsers.
2560 EditorDOMPoint pointToPutCaret
= [&]() {
2561 if (!MayEditActionDeleteAroundCollapsedSelection(
2562 aHTMLEditor
.GetEditAction())) {
2563 return EditorDOMPoint();
2565 WSRunScanner
scanner(&aEditingHost
, EditorRawDOMPoint(mBRElement
),
2566 BlockInlineCheck::UseComputedDisplayOutsideStyle
);
2567 WSScanResult maybePreviousText
=
2568 scanner
.ScanPreviousVisibleNodeOrBlockBoundaryFrom(
2569 EditorRawDOMPoint(mBRElement
));
2570 if (maybePreviousText
.IsContentEditable() &&
2571 maybePreviousText
.InVisibleOrCollapsibleCharacters() &&
2572 !HTMLEditor::GetLinkElement(maybePreviousText
.TextPtr())) {
2573 return maybePreviousText
.Point
<EditorDOMPoint
>();
2575 WSScanResult maybeNextText
= scanner
.ScanNextVisibleNodeOrBlockBoundaryFrom(
2576 EditorRawDOMPoint::After(*mBRElement
));
2577 if (maybeNextText
.IsContentEditable() &&
2578 maybeNextText
.InVisibleOrCollapsibleCharacters()) {
2579 return maybeNextText
.Point
<EditorDOMPoint
>();
2581 return EditorDOMPoint();
2584 // If we found a `<br>` element, we should delete it instead of joining the
2587 aHTMLEditor
.DeleteNodeWithTransaction(MOZ_KnownLive(*mBRElement
));
2588 if (NS_FAILED(rv
)) {
2589 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
2593 if (mLeftContent
&& mRightContent
&&
2594 HTMLEditUtils::GetInclusiveAncestorAnyTableElement(*mLeftContent
) !=
2595 HTMLEditUtils::GetInclusiveAncestorAnyTableElement(*mRightContent
)) {
2596 return EditActionResult::HandledResult();
2599 // Put selection at edge of block and we are done.
2600 if (NS_WARN_IF(!mLeafContentInOtherBlock
)) {
2601 // XXX This must be odd case. The other block can be empty.
2602 return Err(NS_ERROR_FAILURE
);
2605 if (pointToPutCaret
.IsSet()) {
2606 nsresult rv
= aHTMLEditor
.CollapseSelectionTo(pointToPutCaret
);
2607 if (NS_WARN_IF(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
2608 return Err(NS_ERROR_EDITOR_DESTROYED
);
2610 if (NS_SUCCEEDED(rv
)) {
2611 // If we prefer to use style in the previous line, we should forget
2612 // previous styles since the caret position has all styles which we want
2613 // to use with new content.
2614 if (nsIEditor::DirectionIsBackspace(aDirectionAndAmount
)) {
2615 aHTMLEditor
.TopLevelEditSubActionDataRef()
2616 .mCachedPendingStyles
->Clear();
2618 // And we don't want to keep extending a link at ex-end of the previous
2620 if (HTMLEditor::GetLinkElement(pointToPutCaret
.GetContainer())) {
2621 aHTMLEditor
.mPendingStylesToApplyToNewContent
2622 ->ClearLinkAndItsSpecifiedStyle();
2625 NS_WARNING("EditorBase::CollapseSelectionTo() failed, but ignored");
2627 return EditActionResult::HandledResult();
2630 EditorRawDOMPoint newCaretPosition
=
2631 HTMLEditUtils::GetGoodCaretPointFor
<EditorRawDOMPoint
>(
2632 *mLeafContentInOtherBlock
, aDirectionAndAmount
);
2633 if (MOZ_UNLIKELY(!newCaretPosition
.IsSet())) {
2634 NS_WARNING("HTMLEditUtils::GetGoodCaretPointFor() failed");
2635 return Err(NS_ERROR_FAILURE
);
2637 rv
= aHTMLEditor
.CollapseSelectionTo(newCaretPosition
);
2638 if (NS_WARN_IF(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
2639 return Err(NS_ERROR_EDITOR_DESTROYED
);
2641 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
2642 "EditorBase::CollapseSelectionTo() failed, but ignored");
2643 return EditActionResult::HandledResult();
2646 nsresult
HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
2647 ComputeRangeToDeleteAtOtherBlockBoundary(
2648 const HTMLEditor
& aHTMLEditor
,
2649 nsIEditor::EDirection aDirectionAndAmount
,
2650 const EditorDOMPoint
& aCaretPoint
, nsRange
& aRangeToDelete
,
2651 const Element
& aEditingHost
) const {
2652 MOZ_ASSERT(aHTMLEditor
.IsEditActionDataAvailable());
2653 MOZ_ASSERT(aCaretPoint
.IsSetAndValid());
2654 MOZ_ASSERT(mLeftContent
);
2655 MOZ_ASSERT(mRightContent
);
2657 if (HTMLEditUtils::GetInclusiveAncestorAnyTableElement(*mLeftContent
) !=
2658 HTMLEditUtils::GetInclusiveAncestorAnyTableElement(*mRightContent
)) {
2659 if (!mDeleteRangesHandlerConst
.CanFallbackToDeleteRangeWithTransaction(
2661 nsresult rv
= aRangeToDelete
.CollapseTo(aCaretPoint
.ToRawRangeBoundary());
2662 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
), "nsRange::CollapseTo() failed");
2665 nsresult rv
= mDeleteRangesHandlerConst
2666 .FallbackToComputeRangeToDeleteRangeWithTransaction(
2667 aHTMLEditor
, aRangeToDelete
, aEditingHost
);
2668 NS_WARNING_ASSERTION(
2670 "AutoDeleteRangesHandler::"
2671 "FallbackToComputeRangeToDeleteRangeWithTransaction() failed");
2675 AutoInclusiveAncestorBlockElementsJoiner
joiner(*mLeftContent
,
2677 Result
<bool, nsresult
> canJoinThem
=
2678 joiner
.Prepare(aHTMLEditor
, aEditingHost
);
2679 if (canJoinThem
.isErr()) {
2680 NS_WARNING("AutoInclusiveAncestorBlockElementsJoiner::Prepare() failed");
2681 return canJoinThem
.unwrapErr();
2683 if (canJoinThem
.inspect() && joiner
.CanJoinBlocks() &&
2684 !joiner
.ShouldDeleteLeafContentInstead()) {
2686 joiner
.ComputeRangeToDelete(aHTMLEditor
, aCaretPoint
, aRangeToDelete
);
2687 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
2688 "AutoInclusiveAncestorBlockElementsJoiner::"
2689 "ComputeRangeToDelete() failed");
2693 // If AutoInclusiveAncestorBlockElementsJoiner didn't handle it and it's not
2694 // canceled, user may want to modify the start leaf node or the last leaf
2695 // node of the block.
2696 if (mLeafContentInOtherBlock
== aCaretPoint
.GetContainer()) {
2700 AutoHideSelectionChanges
hideSelectionChanges(aHTMLEditor
.SelectionRef());
2702 // If it's ignored, it didn't modify the DOM tree. In this case, user must
2703 // want to delete nearest leaf node in the other block element.
2704 // TODO: We need to consider this before calling ComputeRangesToDelete() for
2705 // computing the deleting range.
2706 EditorRawDOMPoint newCaretPoint
=
2707 aDirectionAndAmount
== nsIEditor::ePrevious
2708 ? EditorRawDOMPoint::AtEndOf(*mLeafContentInOtherBlock
)
2709 : EditorRawDOMPoint(mLeafContentInOtherBlock
, 0);
2710 // If new caret position is same as current caret position, we can do
2712 if (aRangeToDelete
.Collapsed() &&
2713 aRangeToDelete
.EndRef() == newCaretPoint
.ToRawRangeBoundary()) {
2716 // TODO: Stop modifying the `Selection` for computing the target ranges.
2717 nsresult rv
= aHTMLEditor
.CollapseSelectionTo(newCaretPoint
);
2718 if (MOZ_UNLIKELY(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
2720 "EditorBase::CollapseSelectionTo() caused destroying the editor");
2721 return NS_ERROR_EDITOR_DESTROYED
;
2723 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
2724 "EditorBase::CollapseSelectionTo() failed");
2725 if (NS_SUCCEEDED(rv
)) {
2726 AutoRangeArray
rangeArray(aHTMLEditor
.SelectionRef());
2727 AutoDeleteRangesHandler
anotherHandler(mDeleteRangesHandlerConst
);
2728 rv
= anotherHandler
.ComputeRangesToDelete(aHTMLEditor
, aDirectionAndAmount
,
2729 rangeArray
, aEditingHost
);
2730 if (NS_SUCCEEDED(rv
)) {
2731 if (MOZ_LIKELY(!rangeArray
.Ranges().IsEmpty())) {
2732 MOZ_ASSERT(rangeArray
.Ranges().Length() == 1);
2733 aRangeToDelete
.SetStartAndEnd(rangeArray
.FirstRangeRef()->StartRef(),
2734 rangeArray
.FirstRangeRef()->EndRef());
2737 "Recursive AutoDeleteRangesHandler::ComputeRangesToDelete() "
2738 "returned no range");
2739 rv
= NS_ERROR_FAILURE
;
2743 "Recursive AutoDeleteRangesHandler::ComputeRangesToDelete() failed");
2746 // Restore selection.
2747 nsresult rvCollapsingSelectionTo
=
2748 aHTMLEditor
.CollapseSelectionTo(aCaretPoint
);
2749 if (MOZ_UNLIKELY(rvCollapsingSelectionTo
== NS_ERROR_EDITOR_DESTROYED
)) {
2751 "EditorBase::CollapseSelectionTo() caused destroying the editor");
2752 return NS_ERROR_EDITOR_DESTROYED
;
2754 NS_WARNING_ASSERTION(
2755 NS_SUCCEEDED(rvCollapsingSelectionTo
),
2756 "EditorBase::CollapseSelectionTo() failed to restore caret position");
2757 return NS_SUCCEEDED(rv
) && NS_SUCCEEDED(rvCollapsingSelectionTo
)
2762 Result
<EditActionResult
, nsresult
> HTMLEditor::AutoDeleteRangesHandler::
2763 AutoBlockElementsJoiner::HandleDeleteAtOtherBlockBoundary(
2764 HTMLEditor
& aHTMLEditor
, nsIEditor::EDirection aDirectionAndAmount
,
2765 nsIEditor::EStripWrappers aStripWrappers
,
2766 const EditorDOMPoint
& aCaretPoint
, nsRange
& aRangeToDelete
,
2767 const Element
& aEditingHost
) {
2768 MOZ_ASSERT(aHTMLEditor
.IsEditActionDataAvailable());
2769 MOZ_ASSERT(aCaretPoint
.IsSetAndValid());
2770 MOZ_ASSERT(mDeleteRangesHandler
);
2771 MOZ_ASSERT(mLeftContent
);
2772 MOZ_ASSERT(mRightContent
);
2774 if (HTMLEditUtils::GetInclusiveAncestorAnyTableElement(*mLeftContent
) !=
2775 HTMLEditUtils::GetInclusiveAncestorAnyTableElement(*mRightContent
)) {
2776 // If we have not deleted `<br>` element and are not called recursively,
2777 // we should call `DeleteRangesWithTransaction()` here.
2778 if (!mDeleteRangesHandler
->CanFallbackToDeleteRangeWithTransaction(
2780 return EditActionResult::IgnoredResult();
2782 Result
<CaretPoint
, nsresult
> caretPointOrError
=
2783 mDeleteRangesHandler
->FallbackToDeleteRangeWithTransaction(
2784 aHTMLEditor
, aRangeToDelete
);
2785 if (MOZ_UNLIKELY(caretPointOrError
.isErr())) {
2787 "AutoDeleteRangesHandler::FallbackToDeleteRangesWithTransaction() "
2789 return caretPointOrError
.propagateErr();
2791 nsresult rv
= caretPointOrError
.inspect().SuggestCaretPointTo(
2792 aHTMLEditor
, {SuggestCaret::OnlyIfHasSuggestion
,
2793 SuggestCaret::OnlyIfTransactionsAllowedToDoIt
,
2794 SuggestCaret::AndIgnoreTrivialError
});
2795 if (NS_FAILED(rv
)) {
2796 NS_WARNING("CaretPoint::SuggestCaretPointTo() failed");
2799 NS_WARNING_ASSERTION(
2800 rv
!= NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR
,
2801 "CaretPoint::SuggestCaretPointTo() failed, but ignored");
2802 // Don't return "ignored" to avoid to fall it back to delete ranges
2804 return EditActionResult::HandledResult();
2807 // Else we are joining content to block
2808 AutoInclusiveAncestorBlockElementsJoiner
joiner(*mLeftContent
,
2810 Result
<bool, nsresult
> canJoinThem
=
2811 joiner
.Prepare(aHTMLEditor
, aEditingHost
);
2812 if (MOZ_UNLIKELY(canJoinThem
.isErr())) {
2813 NS_WARNING("AutoInclusiveAncestorBlockElementsJoiner::Prepare() failed");
2814 return canJoinThem
.propagateErr();
2817 if (!canJoinThem
.inspect()) {
2818 nsresult rv
= aHTMLEditor
.CollapseSelectionTo(aCaretPoint
);
2819 if (NS_WARN_IF(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
2820 return Err(NS_ERROR_EDITOR_DESTROYED
);
2822 NS_WARNING_ASSERTION(
2824 "EditorBase::CollapseSelectionTo() failed, but ignored");
2825 return EditActionResult::CanceledResult();
2828 auto result
= EditActionResult::IgnoredResult();
2829 EditorDOMPoint
pointToPutCaret(aCaretPoint
);
2830 if (joiner
.CanJoinBlocks()) {
2832 AutoTrackDOMPoint
tracker(aHTMLEditor
.RangeUpdaterRef(),
2834 Result
<EditActionResult
, nsresult
> joinResult
=
2835 joiner
.Run(aHTMLEditor
, aEditingHost
);
2836 if (MOZ_UNLIKELY(joinResult
.isErr())) {
2837 NS_WARNING("AutoInclusiveAncestorBlockElementsJoiner::Run() failed");
2840 result
|= joinResult
.unwrap();
2842 if (joiner
.ShouldDeleteLeafContentInstead()) {
2845 "Assumed `AutoInclusiveAncestorBlockElementsJoiner::Run()` "
2846 "returning ignored, but returned not ignored");
2850 "Assumed `AutoInclusiveAncestorBlockElementsJoiner::Run()` "
2851 "returning handled, but returned ignored");
2853 #endif // #ifdef DEBUG
2854 // If we're deleting selection (not replacing with new content) and
2855 // AutoInclusiveAncestorBlockElementsJoiner computed new caret position,
2856 // we should use it. Otherwise, we should keep the our traditional
2858 if (result
.Handled() && joiner
.PointRefToPutCaret().IsSet()) {
2860 aHTMLEditor
.CollapseSelectionTo(joiner
.PointRefToPutCaret());
2861 if (NS_WARN_IF(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
2862 return Err(NS_ERROR_EDITOR_DESTROYED
);
2864 if (NS_FAILED(rv
)) {
2865 NS_WARNING("EditorBase::CollapseSelectionTo() failed, but ignored");
2868 // If we prefer to use style in the previous line, we should forget
2869 // previous styles since the caret position has all styles which we want
2870 // to use with new content.
2871 if (nsIEditor::DirectionIsBackspace(aDirectionAndAmount
)) {
2872 aHTMLEditor
.TopLevelEditSubActionDataRef()
2873 .mCachedPendingStyles
->Clear();
2875 // And we don't want to keep extending a link at ex-end of the previous
2877 if (HTMLEditor::GetLinkElement(
2878 joiner
.PointRefToPutCaret().GetContainer())) {
2879 aHTMLEditor
.mPendingStylesToApplyToNewContent
2880 ->ClearLinkAndItsSpecifiedStyle();
2886 // If AutoInclusiveAncestorBlockElementsJoiner didn't handle it and it's not
2887 // canceled, user may want to modify the start leaf node or the last leaf
2888 // node of the block.
2889 if (result
.Ignored() &&
2890 mLeafContentInOtherBlock
!= aCaretPoint
.GetContainer()) {
2891 // If it's ignored, it didn't modify the DOM tree. In this case, user
2892 // must want to delete nearest leaf node in the other block element.
2893 // TODO: We need to consider this before calling Run() for computing the
2895 EditorRawDOMPoint newCaretPoint
=
2896 aDirectionAndAmount
== nsIEditor::ePrevious
2897 ? EditorRawDOMPoint::AtEndOf(*mLeafContentInOtherBlock
)
2898 : EditorRawDOMPoint(mLeafContentInOtherBlock
, 0);
2899 // If new caret position is same as current caret position, we can do
2901 if (aRangeToDelete
.Collapsed() &&
2902 aRangeToDelete
.EndRef() == newCaretPoint
.ToRawRangeBoundary()) {
2903 return EditActionResult::CanceledResult();
2905 nsresult rv
= aHTMLEditor
.CollapseSelectionTo(newCaretPoint
);
2906 if (NS_FAILED(rv
)) {
2907 NS_WARNING("EditorBase::CollapseSelectionTo() failed");
2910 AutoRangeArray
rangesToDelete(aHTMLEditor
.SelectionRef());
2911 AutoDeleteRangesHandler
anotherHandler(mDeleteRangesHandler
);
2912 Result
<EditActionResult
, nsresult
> fallbackResult
=
2913 anotherHandler
.Run(aHTMLEditor
, aDirectionAndAmount
, aStripWrappers
,
2914 rangesToDelete
, aEditingHost
);
2915 if (MOZ_UNLIKELY(fallbackResult
.isErr())) {
2916 NS_WARNING("Recursive AutoDeleteRangesHandler::Run() failed");
2917 return fallbackResult
;
2919 result
|= fallbackResult
.unwrap();
2923 result
.MarkAsHandled();
2926 // Otherwise, we must have deleted the selection as user expected.
2927 nsresult rv
= aHTMLEditor
.CollapseSelectionTo(pointToPutCaret
);
2928 if (NS_WARN_IF(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
2929 return Err(NS_ERROR_EDITOR_DESTROYED
);
2931 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
2932 "EditorBase::CollapseSelectionTo() failed, but ignored");
2936 bool HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
2937 PrepareToDeleteAtCurrentBlockBoundary(
2938 const HTMLEditor
& aHTMLEditor
,
2939 nsIEditor::EDirection aDirectionAndAmount
,
2940 Element
& aCurrentBlockElement
, const EditorDOMPoint
& aCaretPoint
) {
2941 MOZ_ASSERT(aHTMLEditor
.IsEditActionDataAvailable());
2943 // At edge of our block. Look beside it and see if we can join to an
2945 mMode
= Mode::JoinCurrentBlock
;
2947 // Don't break the basic structure of the HTML document.
2948 if (aCurrentBlockElement
.IsAnyOfHTMLElements(nsGkAtoms::html
, nsGkAtoms::head
,
2953 // Make sure it's not a table element. If so, cancel the operation
2954 // (translation: users cannot backspace or delete across table cells)
2955 if (HTMLEditUtils::IsAnyTableElement(&aCurrentBlockElement
)) {
2959 Element
* editingHost
= aHTMLEditor
.ComputeEditingHost();
2960 if (NS_WARN_IF(!editingHost
)) {
2964 auto ScanJoinTarget
= [&]() -> nsIContent
* {
2965 nsIContent
* targetContent
=
2966 aDirectionAndAmount
== nsIEditor::ePrevious
2967 ? HTMLEditUtils::GetPreviousContent(
2968 aCurrentBlockElement
, {WalkTreeOption::IgnoreNonEditableNode
},
2969 BlockInlineCheck::Unused
, editingHost
)
2970 : HTMLEditUtils::GetNextContent(
2971 aCurrentBlockElement
, {WalkTreeOption::IgnoreNonEditableNode
},
2972 BlockInlineCheck::Unused
, editingHost
);
2973 // If found content is an invisible text node, let's scan visible things.
2974 auto IsIgnorableDataNode
= [](nsIContent
* aContent
) {
2975 return aContent
&& HTMLEditUtils::IsRemovableNode(*aContent
) &&
2976 ((aContent
->IsText() &&
2977 aContent
->AsText()->TextIsOnlyWhitespace() &&
2978 !HTMLEditUtils::IsVisibleTextNode(*aContent
->AsText())) ||
2979 (aContent
->IsCharacterData() && !aContent
->IsText()));
2981 if (!IsIgnorableDataNode(targetContent
)) {
2982 return targetContent
;
2984 MOZ_ASSERT(mSkippedInvisibleContents
.IsEmpty());
2985 for (nsIContent
* adjacentContent
=
2986 aDirectionAndAmount
== nsIEditor::ePrevious
2987 ? HTMLEditUtils::GetPreviousContent(
2988 *targetContent
, {WalkTreeOption::StopAtBlockBoundary
},
2989 BlockInlineCheck::UseComputedDisplayOutsideStyle
,
2991 : HTMLEditUtils::GetNextContent(
2992 *targetContent
, {WalkTreeOption::StopAtBlockBoundary
},
2993 BlockInlineCheck::UseComputedDisplayOutsideStyle
,
2997 aDirectionAndAmount
== nsIEditor::ePrevious
2998 ? HTMLEditUtils::GetPreviousContent(
2999 *adjacentContent
, {WalkTreeOption::StopAtBlockBoundary
},
3000 BlockInlineCheck::UseComputedDisplayOutsideStyle
,
3002 : HTMLEditUtils::GetNextContent(
3003 *adjacentContent
, {WalkTreeOption::StopAtBlockBoundary
},
3004 BlockInlineCheck::UseComputedDisplayOutsideStyle
,
3006 // If non-editable element is found, we should not skip it to avoid
3007 // joining too far nodes.
3008 if (!HTMLEditUtils::IsSimplyEditableNode(*adjacentContent
)) {
3011 // If block element is found, we should join last leaf content in it.
3012 if (HTMLEditUtils::IsBlockElement(
3014 BlockInlineCheck::UseComputedDisplayOutsideStyle
)) {
3015 nsIContent
* leafContent
=
3016 aDirectionAndAmount
== nsIEditor::ePrevious
3017 ? HTMLEditUtils::GetLastLeafContent(
3018 *adjacentContent
, {LeafNodeType::OnlyEditableLeafNode
})
3019 : HTMLEditUtils::GetFirstLeafContent(
3020 *adjacentContent
, {LeafNodeType::OnlyEditableLeafNode
});
3021 mSkippedInvisibleContents
.AppendElement(*targetContent
);
3022 return leafContent
? leafContent
: adjacentContent
;
3024 // Only when the found node is an invisible text node or a non-text data
3025 // node, we should keep scanning.
3026 if (IsIgnorableDataNode(adjacentContent
)) {
3027 mSkippedInvisibleContents
.AppendElement(*targetContent
);
3028 targetContent
= adjacentContent
;
3031 // Otherwise, we find a visible things. We should join with last found
3032 // invisible text node.
3035 return targetContent
;
3038 if (aDirectionAndAmount
== nsIEditor::ePrevious
) {
3039 mLeftContent
= ScanJoinTarget();
3040 mRightContent
= aCaretPoint
.GetContainerAs
<nsIContent
>();
3042 mRightContent
= ScanJoinTarget();
3043 mLeftContent
= aCaretPoint
.GetContainerAs
<nsIContent
>();
3047 if (!mLeftContent
|| !mRightContent
) {
3051 // Don't cross table boundaries.
3052 return HTMLEditUtils::GetInclusiveAncestorAnyTableElement(*mLeftContent
) ==
3053 HTMLEditUtils::GetInclusiveAncestorAnyTableElement(*mRightContent
);
3056 nsresult
HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
3057 ComputeRangeToDeleteAtCurrentBlockBoundary(
3058 const HTMLEditor
& aHTMLEditor
, const EditorDOMPoint
& aCaretPoint
,
3059 nsRange
& aRangeToDelete
, const Element
& aEditingHost
) const {
3060 MOZ_ASSERT(mLeftContent
);
3061 MOZ_ASSERT(mRightContent
);
3063 AutoInclusiveAncestorBlockElementsJoiner
joiner(*mLeftContent
,
3065 Result
<bool, nsresult
> canJoinThem
=
3066 joiner
.Prepare(aHTMLEditor
, aEditingHost
);
3067 if (canJoinThem
.isErr()) {
3068 NS_WARNING("AutoInclusiveAncestorBlockElementsJoiner::Prepare() failed");
3069 return canJoinThem
.unwrapErr();
3071 if (canJoinThem
.inspect()) {
3073 joiner
.ComputeRangeToDelete(aHTMLEditor
, aCaretPoint
, aRangeToDelete
);
3074 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
3075 "AutoInclusiveAncestorBlockElementsJoiner::"
3076 "ComputeRangesToDelete() failed");
3080 // In this case, nothing will be deleted so that the affected range should
3082 nsresult rv
= aRangeToDelete
.CollapseTo(aCaretPoint
.ToRawRangeBoundary());
3083 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
), "nsRange::CollapseTo() failed");
3087 Result
<EditActionResult
, nsresult
> HTMLEditor::AutoDeleteRangesHandler::
3088 AutoBlockElementsJoiner::HandleDeleteAtCurrentBlockBoundary(
3089 HTMLEditor
& aHTMLEditor
, nsIEditor::EDirection aDirectionAndAmount
,
3090 const EditorDOMPoint
& aCaretPoint
, const Element
& aEditingHost
) {
3091 MOZ_ASSERT(mLeftContent
);
3092 MOZ_ASSERT(mRightContent
);
3094 AutoInclusiveAncestorBlockElementsJoiner
joiner(*mLeftContent
,
3096 Result
<bool, nsresult
> canJoinThem
=
3097 joiner
.Prepare(aHTMLEditor
, aEditingHost
);
3098 if (MOZ_UNLIKELY(canJoinThem
.isErr())) {
3099 NS_WARNING("AutoInclusiveAncestorBlockElementsJoiner::Prepare() failed");
3100 return Err(canJoinThem
.unwrapErr());
3103 if (!canJoinThem
.inspect()) {
3104 nsresult rv
= aHTMLEditor
.CollapseSelectionTo(aCaretPoint
);
3105 if (NS_WARN_IF(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
3106 return Err(NS_ERROR_EDITOR_DESTROYED
);
3108 NS_WARNING_ASSERTION(
3110 "EditorBase::CollapseSelectionTo() failed, but ignored");
3111 return EditActionResult::CanceledResult();
3114 EditActionResult result
= EditActionResult::IgnoredResult();
3115 EditorDOMPoint
pointToPutCaret(aCaretPoint
);
3116 if (joiner
.CanJoinBlocks()) {
3117 AutoTrackDOMPoint
tracker(aHTMLEditor
.RangeUpdaterRef(), &pointToPutCaret
);
3118 Result
<EditActionResult
, nsresult
> joinResult
=
3119 joiner
.Run(aHTMLEditor
, aEditingHost
);
3120 if (MOZ_UNLIKELY(joinResult
.isErr())) {
3121 NS_WARNING("AutoInclusiveAncestorBlockElementsJoiner::Run() failed");
3124 result
|= joinResult
.unwrap();
3126 if (joiner
.ShouldDeleteLeafContentInstead()) {
3127 NS_ASSERTION(result
.Ignored(),
3128 "Assumed `AutoInclusiveAncestorBlockElementsJoiner::Run()` "
3129 "returning ignored, but returned not ignored");
3131 NS_ASSERTION(!result
.Ignored(),
3132 "Assumed `AutoInclusiveAncestorBlockElementsJoiner::Run()` "
3133 "returning handled, but returned ignored");
3135 #endif // #ifdef DEBUG
3137 // Cleaning up invisible nodes which are skipped at scanning mLeftContent or
3139 for (const OwningNonNull
<nsIContent
>& content
: mSkippedInvisibleContents
) {
3141 aHTMLEditor
.DeleteNodeWithTransaction(MOZ_KnownLive(content
));
3142 if (NS_FAILED(rv
)) {
3143 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
3147 mSkippedInvisibleContents
.Clear();
3149 // If we're deleting selection (not replacing with new content) and
3150 // AutoInclusiveAncestorBlockElementsJoiner computed new caret position, we
3151 // should use it. Otherwise, we should keep the our traditional behavior.
3152 if (result
.Handled() && joiner
.PointRefToPutCaret().IsSet()) {
3154 aHTMLEditor
.CollapseSelectionTo(joiner
.PointRefToPutCaret());
3155 if (NS_WARN_IF(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
3156 return Err(NS_ERROR_EDITOR_DESTROYED
);
3158 if (NS_FAILED(rv
)) {
3159 NS_WARNING("EditorBase::CollapseSelectionTo() failed, but ignored");
3162 // If we prefer to use style in the previous line, we should forget
3163 // previous styles since the caret position has all styles which we want
3164 // to use with new content.
3165 if (nsIEditor::DirectionIsBackspace(aDirectionAndAmount
)) {
3166 aHTMLEditor
.TopLevelEditSubActionDataRef()
3167 .mCachedPendingStyles
->Clear();
3169 // And we don't want to keep extending a link at ex-end of the previous
3171 if (HTMLEditor::GetLinkElement(
3172 joiner
.PointRefToPutCaret().GetContainer())) {
3173 aHTMLEditor
.mPendingStylesToApplyToNewContent
3174 ->ClearLinkAndItsSpecifiedStyle();
3179 // This should claim that trying to join the block means that
3180 // this handles the action because the caller shouldn't do anything
3181 // anymore in this case.
3182 result
.MarkAsHandled();
3184 nsresult rv
= aHTMLEditor
.CollapseSelectionTo(pointToPutCaret
);
3185 if (NS_WARN_IF(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
3186 return Err(NS_ERROR_EDITOR_DESTROYED
);
3188 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
3189 "EditorBase::CollapseSelectionTo() failed, but ignored");
3194 HTMLEditor::AutoDeleteRangesHandler::ComputeRangesToDeleteNonCollapsedRanges(
3195 const HTMLEditor
& aHTMLEditor
, nsIEditor::EDirection aDirectionAndAmount
,
3196 AutoRangeArray
& aRangesToDelete
,
3197 AutoDeleteRangesHandler::SelectionWasCollapsed aSelectionWasCollapsed
,
3198 const Element
& aEditingHost
) const {
3199 MOZ_ASSERT(!aRangesToDelete
.IsCollapsed());
3201 if (NS_WARN_IF(!aRangesToDelete
.FirstRangeRef()->StartRef().IsSet()) ||
3202 NS_WARN_IF(!aRangesToDelete
.FirstRangeRef()->EndRef().IsSet())) {
3203 return NS_ERROR_FAILURE
;
3206 if (aRangesToDelete
.Ranges().Length() == 1) {
3207 nsFrameSelection
* frameSelection
=
3208 aHTMLEditor
.SelectionRef().GetFrameSelection();
3209 if (NS_WARN_IF(!frameSelection
)) {
3210 return NS_ERROR_FAILURE
;
3212 Result
<EditorRawDOMRange
, nsresult
> result
= ExtendOrShrinkRangeToDelete(
3213 aHTMLEditor
, frameSelection
,
3214 EditorRawDOMRange(aRangesToDelete
.FirstRangeRef()));
3215 if (MOZ_UNLIKELY(result
.isErr())) {
3217 "AutoDeleteRangesHandler::ExtendOrShrinkRangeToDelete() failed");
3218 return NS_ERROR_FAILURE
;
3220 EditorRawDOMRange
newRange(result
.unwrap());
3221 if (MOZ_UNLIKELY(NS_FAILED(aRangesToDelete
.FirstRangeRef()->SetStartAndEnd(
3222 newRange
.StartRef().ToRawRangeBoundary(),
3223 newRange
.EndRef().ToRawRangeBoundary())))) {
3224 NS_WARNING("nsRange::SetStartAndEnd() failed");
3225 return NS_ERROR_FAILURE
;
3228 NS_WARN_IF(!aRangesToDelete
.FirstRangeRef()->IsPositioned()))) {
3229 return NS_ERROR_FAILURE
;
3231 if (NS_WARN_IF(aRangesToDelete
.FirstRangeRef()->Collapsed())) {
3232 return NS_OK
; // Hmm, there is nothing to delete...?
3236 if (!aHTMLEditor
.IsPlaintextMailComposer()) {
3237 EditorDOMRange
firstRange(aRangesToDelete
.FirstRangeRef());
3238 EditorDOMRange extendedRange
=
3239 WSRunScanner::GetRangeContainingInvisibleWhiteSpacesAtRangeBoundaries(
3240 aHTMLEditor
.ComputeEditingHost(),
3241 EditorDOMRange(aRangesToDelete
.FirstRangeRef()));
3242 if (firstRange
!= extendedRange
) {
3243 nsresult rv
= aRangesToDelete
.FirstRangeRef()->SetStartAndEnd(
3244 extendedRange
.StartRef().ToRawRangeBoundary(),
3245 extendedRange
.EndRef().ToRawRangeBoundary());
3246 if (NS_FAILED(rv
)) {
3247 NS_WARNING("nsRange::SetStartAndEnd() failed");
3248 return NS_ERROR_FAILURE
;
3253 if (aRangesToDelete
.FirstRangeRef()->GetStartContainer() ==
3254 aRangesToDelete
.FirstRangeRef()->GetEndContainer()) {
3255 if (!aRangesToDelete
.FirstRangeRef()->Collapsed()) {
3256 nsresult rv
= ComputeRangesToDeleteRangesWithTransaction(
3257 aHTMLEditor
, aDirectionAndAmount
, aRangesToDelete
, aEditingHost
);
3258 NS_WARNING_ASSERTION(
3260 "AutoDeleteRangesHandler::ComputeRangesToDeleteRangesWithTransaction("
3264 // `DeleteUnnecessaryNodesAndCollapseSelection()` may delete parent
3265 // elements, but it does not affect computing target ranges. Therefore,
3266 // we don't need to touch aRangesToDelete in this case.
3270 Element
* startCiteNode
= aHTMLEditor
.GetMostDistantAncestorMailCiteElement(
3271 *aRangesToDelete
.FirstRangeRef()->GetStartContainer());
3272 Element
* endCiteNode
= aHTMLEditor
.GetMostDistantAncestorMailCiteElement(
3273 *aRangesToDelete
.FirstRangeRef()->GetEndContainer());
3275 if (startCiteNode
&& !endCiteNode
) {
3276 aDirectionAndAmount
= nsIEditor::eNext
;
3277 } else if (!startCiteNode
&& endCiteNode
) {
3278 aDirectionAndAmount
= nsIEditor::ePrevious
;
3281 for (const OwningNonNull
<nsRange
>& range
: aRangesToDelete
.Ranges()) {
3282 if (MOZ_UNLIKELY(range
->Collapsed())) {
3285 AutoBlockElementsJoiner
joiner(*this);
3286 if (!joiner
.PrepareToDeleteNonCollapsedRange(aHTMLEditor
, range
)) {
3287 return NS_ERROR_FAILURE
;
3290 joiner
.ComputeRangeToDelete(aHTMLEditor
, aDirectionAndAmount
, range
,
3291 aSelectionWasCollapsed
, aEditingHost
);
3292 if (NS_FAILED(rv
)) {
3293 NS_WARNING("AutoBlockElementsJoiner::ComputeRangeToDelete() failed");
3300 Result
<EditActionResult
, nsresult
>
3301 HTMLEditor::AutoDeleteRangesHandler::HandleDeleteNonCollapsedRanges(
3302 HTMLEditor
& aHTMLEditor
, nsIEditor::EDirection aDirectionAndAmount
,
3303 nsIEditor::EStripWrappers aStripWrappers
, AutoRangeArray
& aRangesToDelete
,
3304 SelectionWasCollapsed aSelectionWasCollapsed
, const Element
& aEditingHost
) {
3305 MOZ_ASSERT(aHTMLEditor
.IsTopLevelEditSubActionDataAvailable());
3306 MOZ_ASSERT(!aRangesToDelete
.IsCollapsed());
3308 if (NS_WARN_IF(!aRangesToDelete
.FirstRangeRef()->StartRef().IsSet()) ||
3309 NS_WARN_IF(!aRangesToDelete
.FirstRangeRef()->EndRef().IsSet())) {
3310 return Err(NS_ERROR_FAILURE
);
3313 MOZ_ASSERT_IF(aRangesToDelete
.Ranges().Length() == 1,
3314 aRangesToDelete
.IsFirstRangeEditable(aEditingHost
));
3316 // Else we have a non-collapsed selection. First adjust the selection.
3317 // XXX Why do we extend selection only when there is only one range?
3318 if (aRangesToDelete
.Ranges().Length() == 1) {
3319 nsFrameSelection
* frameSelection
=
3320 aHTMLEditor
.SelectionRef().GetFrameSelection();
3321 if (NS_WARN_IF(!frameSelection
)) {
3322 return Err(NS_ERROR_FAILURE
);
3324 Result
<EditorRawDOMRange
, nsresult
> result
= ExtendOrShrinkRangeToDelete(
3325 aHTMLEditor
, frameSelection
,
3326 EditorRawDOMRange(aRangesToDelete
.FirstRangeRef()));
3327 if (MOZ_UNLIKELY(result
.isErr())) {
3329 "AutoDeleteRangesHandler::ExtendOrShrinkRangeToDelete() failed");
3330 return Err(NS_ERROR_FAILURE
);
3332 EditorRawDOMRange
newRange(result
.unwrap());
3333 if (NS_FAILED(aRangesToDelete
.FirstRangeRef()->SetStartAndEnd(
3334 newRange
.StartRef().ToRawRangeBoundary(),
3335 newRange
.EndRef().ToRawRangeBoundary()))) {
3336 NS_WARNING("nsRange::SetStartAndEnd() failed");
3337 return Err(NS_ERROR_FAILURE
);
3339 if (NS_WARN_IF(!aRangesToDelete
.FirstRangeRef()->IsPositioned())) {
3340 return Err(NS_ERROR_FAILURE
);
3342 if (NS_WARN_IF(aRangesToDelete
.FirstRangeRef()->Collapsed())) {
3343 // Hmm, there is nothing to delete...?
3344 // In this case, the callers want collapsed selection. Therefore, we need
3345 // to change the `Selection` here.
3346 nsresult rv
= aHTMLEditor
.CollapseSelectionTo(
3347 aRangesToDelete
.GetFirstRangeStartPoint
<EditorRawDOMPoint
>());
3348 if (NS_FAILED(rv
)) {
3349 NS_WARNING("EditorBase::CollapseSelectionTo() failed");
3352 return EditActionResult::HandledResult();
3354 MOZ_ASSERT(aRangesToDelete
.IsFirstRangeEditable(aEditingHost
));
3357 // Remember that we did a ranged delete for the benefit of AfterEditInner().
3358 aHTMLEditor
.TopLevelEditSubActionDataRef().mDidDeleteNonCollapsedRange
= true;
3360 // Figure out if the endpoints are in nodes that can be merged. Adjust
3361 // surrounding white-space in preparation to delete selection.
3362 if (!aHTMLEditor
.IsPlaintextMailComposer()) {
3364 AutoTrackDOMRange
firstRangeTracker(aHTMLEditor
.RangeUpdaterRef(),
3365 &aRangesToDelete
.FirstRangeRef());
3366 Result
<CaretPoint
, nsresult
> caretPointOrError
=
3367 WhiteSpaceVisibilityKeeper::PrepareToDeleteRange(
3368 aHTMLEditor
, EditorDOMRange(aRangesToDelete
.FirstRangeRef()),
3370 if (MOZ_UNLIKELY(caretPointOrError
.isErr())) {
3371 NS_WARNING("WhiteSpaceVisibilityKeeper::PrepareToDeleteRange() failed");
3372 return caretPointOrError
.propagateErr();
3374 // Ignore caret point suggestion because there was
3375 // AutoTransactionsConserveSelection.
3376 caretPointOrError
.unwrap().IgnoreCaretPointSuggestion();
3378 if (NS_WARN_IF(!aRangesToDelete
.FirstRangeRef()->IsPositioned()) ||
3379 (aHTMLEditor
.MayHaveMutationEventListeners() &&
3380 NS_WARN_IF(!aRangesToDelete
.IsFirstRangeEditable(aEditingHost
)))) {
3382 "WhiteSpaceVisibilityKeeper::PrepareToDeleteRange() made the first "
3384 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
);
3388 // XXX This is odd. We do we simply use `DeleteRangesWithTransaction()`
3389 // only when **first** range is in same container?
3390 if (aRangesToDelete
.FirstRangeRef()->GetStartContainer() ==
3391 aRangesToDelete
.FirstRangeRef()->GetEndContainer()) {
3392 // Because of previous DOM tree changes, the range may be collapsed.
3393 // If we've already removed all contents in the range, we shouldn't
3394 // delete anything around the caret.
3395 if (!aRangesToDelete
.FirstRangeRef()->Collapsed()) {
3397 AutoTrackDOMRange
firstRangeTracker(aHTMLEditor
.RangeUpdaterRef(),
3398 &aRangesToDelete
.FirstRangeRef());
3399 Result
<CaretPoint
, nsresult
> caretPointOrError
=
3400 aHTMLEditor
.DeleteRangesWithTransaction(
3401 aDirectionAndAmount
, aStripWrappers
, aRangesToDelete
);
3402 if (MOZ_UNLIKELY(caretPointOrError
.isErr())) {
3403 NS_WARNING("EditorBase::DeleteRangesWithTransaction() failed");
3404 return caretPointOrError
.propagateErr();
3406 nsresult rv
= caretPointOrError
.inspect().SuggestCaretPointTo(
3407 aHTMLEditor
, {SuggestCaret::OnlyIfHasSuggestion
,
3408 SuggestCaret::OnlyIfTransactionsAllowedToDoIt
,
3409 SuggestCaret::AndIgnoreTrivialError
});
3410 if (NS_FAILED(rv
)) {
3411 NS_WARNING("CaretPoint::SuggestCaretPointTo() failed");
3414 NS_WARNING_ASSERTION(
3415 rv
!= NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR
,
3416 "CaretPoint::SuggestCaretPointTo() failed, but ignored");
3418 if (NS_WARN_IF(!aRangesToDelete
.FirstRangeRef()->IsPositioned()) ||
3419 (aHTMLEditor
.MayHaveMutationEventListeners(
3420 NS_EVENT_BITS_MUTATION_NODEREMOVED
|
3421 NS_EVENT_BITS_MUTATION_NODEREMOVEDFROMDOCUMENT
|
3422 NS_EVENT_BITS_MUTATION_SUBTREEMODIFIED
) &&
3423 NS_WARN_IF(!aRangesToDelete
.IsFirstRangeEditable(aEditingHost
)))) {
3425 "EditorBase::DeleteRangesWithTransaction() made the first range "
3427 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
);
3430 // However, even if the range is removed, we may need to clean up the
3431 // containers which become empty.
3432 nsresult rv
= DeleteUnnecessaryNodesAndCollapseSelection(
3433 aHTMLEditor
, aDirectionAndAmount
,
3434 EditorDOMPoint(aRangesToDelete
.FirstRangeRef()->StartRef()),
3435 EditorDOMPoint(aRangesToDelete
.FirstRangeRef()->EndRef()));
3436 if (NS_FAILED(rv
)) {
3438 "AutoDeleteRangesHandler::DeleteUnnecessaryNodesAndCollapseSelection("
3442 return EditActionResult::HandledResult();
3446 !aRangesToDelete
.FirstRangeRef()->GetStartContainer()->IsContent()) ||
3448 !aRangesToDelete
.FirstRangeRef()->GetEndContainer()->IsContent())) {
3449 return Err(NS_ERROR_FAILURE
);
3452 // Figure out mailcite ancestors
3453 RefPtr
<Element
> startCiteNode
=
3454 aHTMLEditor
.GetMostDistantAncestorMailCiteElement(
3455 *aRangesToDelete
.FirstRangeRef()->GetStartContainer());
3456 RefPtr
<Element
> endCiteNode
=
3457 aHTMLEditor
.GetMostDistantAncestorMailCiteElement(
3458 *aRangesToDelete
.FirstRangeRef()->GetEndContainer());
3460 // If we only have a mailcite at one of the two endpoints, set the
3461 // directionality of the deletion so that the selection will end up
3462 // outside the mailcite.
3463 if (startCiteNode
&& !endCiteNode
) {
3464 aDirectionAndAmount
= nsIEditor::eNext
;
3465 } else if (!startCiteNode
&& endCiteNode
) {
3466 aDirectionAndAmount
= nsIEditor::ePrevious
;
3469 MOZ_ASSERT(!aRangesToDelete
.Ranges().IsEmpty());
3470 auto ret
= EditActionResult::IgnoredResult();
3471 for (const OwningNonNull
<nsRange
>& range
: aRangesToDelete
.Ranges()) {
3472 if (MOZ_UNLIKELY(range
->Collapsed())) {
3475 AutoBlockElementsJoiner
joiner(*this);
3476 if (!joiner
.PrepareToDeleteNonCollapsedRange(aHTMLEditor
, range
)) {
3477 return Err(NS_ERROR_FAILURE
);
3479 Result
<EditActionResult
, nsresult
> result
=
3480 joiner
.Run(aHTMLEditor
, aDirectionAndAmount
, aStripWrappers
,
3481 MOZ_KnownLive(range
), aSelectionWasCollapsed
, aEditingHost
);
3482 if (MOZ_UNLIKELY(result
.isErr())) {
3483 NS_WARNING("AutoBlockElementsJoiner::Run() failed");
3486 ret
|= result
.inspect();
3491 bool HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
3492 PrepareToDeleteNonCollapsedRange(const HTMLEditor
& aHTMLEditor
,
3493 const nsRange
& aRangeToDelete
) {
3494 MOZ_ASSERT(aHTMLEditor
.IsEditActionDataAvailable());
3495 MOZ_ASSERT(!aRangeToDelete
.Collapsed());
3497 mLeftContent
= HTMLEditUtils::GetInclusiveAncestorElement(
3498 *aRangeToDelete
.GetStartContainer()->AsContent(),
3499 HTMLEditUtils::ClosestEditableBlockElement
,
3500 BlockInlineCheck::UseComputedDisplayOutsideStyle
);
3501 mRightContent
= HTMLEditUtils::GetInclusiveAncestorElement(
3502 *aRangeToDelete
.GetEndContainer()->AsContent(),
3503 HTMLEditUtils::ClosestEditableBlockElement
,
3504 BlockInlineCheck::UseComputedDisplayOutsideStyle
);
3505 // Note that mLeftContent and/or mRightContent can be nullptr if editing host
3506 // is an inline element. If both editable ancestor block is exactly same
3507 // one or one reaches an inline editing host, we can just delete the content
3509 if (mLeftContent
== mRightContent
|| !mLeftContent
|| !mRightContent
) {
3511 !mLeftContent
|| !mRightContent
,
3512 aRangeToDelete
.GetStartContainer()->AsContent()->GetEditingHost() ==
3513 aRangeToDelete
.GetEndContainer()->AsContent()->GetEditingHost());
3514 mMode
= Mode::DeleteContentInRange
;
3518 // If left block and right block are adjacent siblings and they are same
3519 // type of elements, we can merge them after deleting the selected contents.
3520 // MOOSE: this could conceivably screw up a table.. fix me.
3521 if (mLeftContent
->GetParentNode() == mRightContent
->GetParentNode() &&
3522 HTMLEditUtils::CanContentsBeJoined(*mLeftContent
, *mRightContent
) &&
3523 // XXX What's special about these three types of block?
3524 (mLeftContent
->IsHTMLElement(nsGkAtoms::p
) ||
3525 HTMLEditUtils::IsListItem(mLeftContent
) ||
3526 HTMLEditUtils::IsHeader(*mLeftContent
))) {
3527 mMode
= Mode::JoinBlocksInSameParent
;
3531 mMode
= Mode::DeleteNonCollapsedRange
;
3535 nsresult
HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
3536 ComputeRangeToDeleteContentInRange(
3537 const HTMLEditor
& aHTMLEditor
,
3538 nsIEditor::EDirection aDirectionAndAmount
, nsRange
& aRangeToDelete
,
3539 const Element
& aEditingHost
) const {
3540 MOZ_ASSERT(aHTMLEditor
.IsEditActionDataAvailable());
3541 MOZ_ASSERT(!aRangeToDelete
.Collapsed());
3542 MOZ_ASSERT(mMode
== Mode::DeleteContentInRange
);
3543 MOZ_ASSERT(aRangeToDelete
.GetStartContainer()->AsContent()->GetEditingHost());
3545 aRangeToDelete
.GetStartContainer()->AsContent()->GetEditingHost() ==
3546 aRangeToDelete
.GetEndContainer()->AsContent()->GetEditingHost());
3547 MOZ_ASSERT(!mLeftContent
== !mRightContent
);
3548 MOZ_ASSERT_IF(mLeftContent
, mLeftContent
->IsElement());
3549 MOZ_ASSERT_IF(mLeftContent
,
3550 aRangeToDelete
.GetStartContainer()->IsInclusiveDescendantOf(
3552 MOZ_ASSERT_IF(mRightContent
, mRightContent
->IsElement());
3555 aRangeToDelete
.GetEndContainer()->IsInclusiveDescendantOf(mRightContent
));
3558 HTMLEditUtils::IsInlineContent(
3559 *aRangeToDelete
.GetStartContainer()->AsContent()->GetEditingHost(),
3560 BlockInlineCheck::UseComputedDisplayOutsideStyle
));
3563 mDeleteRangesHandlerConst
.ComputeRangeToDeleteRangeWithTransaction(
3564 aHTMLEditor
, aDirectionAndAmount
, aRangeToDelete
, aEditingHost
);
3565 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
3566 "AutoDeleteRangesHandler::"
3567 "ComputeRangeToDeleteRangeWithTransaction() failed");
3571 Result
<EditActionResult
, nsresult
> HTMLEditor::AutoDeleteRangesHandler::
3572 AutoBlockElementsJoiner::DeleteContentInRange(
3573 HTMLEditor
& aHTMLEditor
, nsIEditor::EDirection aDirectionAndAmount
,
3574 nsIEditor::EStripWrappers aStripWrappers
, nsRange
& aRangeToDelete
) {
3575 MOZ_ASSERT(aHTMLEditor
.IsEditActionDataAvailable());
3576 MOZ_ASSERT(!aRangeToDelete
.Collapsed());
3577 MOZ_ASSERT(mMode
== Mode::DeleteContentInRange
);
3578 MOZ_ASSERT(mDeleteRangesHandler
);
3579 MOZ_ASSERT(aRangeToDelete
.GetStartContainer()->AsContent()->GetEditingHost());
3581 aRangeToDelete
.GetStartContainer()->AsContent()->GetEditingHost() ==
3582 aRangeToDelete
.GetEndContainer()->AsContent()->GetEditingHost());
3583 MOZ_ASSERT_IF(mLeftContent
, mLeftContent
->IsElement());
3584 MOZ_ASSERT_IF(mLeftContent
,
3585 aRangeToDelete
.GetStartContainer()->IsInclusiveDescendantOf(
3587 MOZ_ASSERT_IF(mRightContent
, mRightContent
->IsElement());
3590 aRangeToDelete
.GetEndContainer()->IsInclusiveDescendantOf(mRightContent
));
3593 HTMLEditUtils::IsInlineContent(
3594 *aRangeToDelete
.GetStartContainer()->AsContent()->GetEditingHost(),
3595 BlockInlineCheck::UseComputedDisplayOutsideStyle
));
3598 AutoRangeArray
rangesToDelete(aRangeToDelete
);
3599 AutoTrackDOMRange
firstRangeTracker(aHTMLEditor
.RangeUpdaterRef(),
3600 &rangesToDelete
.FirstRangeRef());
3601 Result
<CaretPoint
, nsresult
> caretPointOrError
=
3602 aHTMLEditor
.DeleteRangesWithTransaction(aDirectionAndAmount
,
3603 aStripWrappers
, rangesToDelete
);
3604 if (MOZ_UNLIKELY(caretPointOrError
.isErr())) {
3605 if (NS_WARN_IF(caretPointOrError
.inspectErr() ==
3606 NS_ERROR_EDITOR_DESTROYED
)) {
3607 return Err(NS_ERROR_EDITOR_DESTROYED
);
3610 "EditorBase::DeleteRangesWithTransaction() failed, but ignored");
3612 nsresult rv
= caretPointOrError
.inspect().SuggestCaretPointTo(
3613 aHTMLEditor
, {SuggestCaret::OnlyIfHasSuggestion
,
3614 SuggestCaret::OnlyIfTransactionsAllowedToDoIt
,
3615 SuggestCaret::AndIgnoreTrivialError
});
3616 if (NS_FAILED(rv
)) {
3617 NS_WARNING("CaretPoint::SuggestCaretPointTo() failed");
3620 NS_WARNING_ASSERTION(
3621 rv
!= NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR
,
3622 "CaretPoint::SuggestCaretPointTo() failed, but ignored");
3626 if (NS_WARN_IF(!aRangeToDelete
.IsPositioned())) {
3627 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
);
3631 mDeleteRangesHandler
->DeleteUnnecessaryNodesAndCollapseSelection(
3632 aHTMLEditor
, aDirectionAndAmount
,
3633 EditorDOMPoint(aRangeToDelete
.StartRef()),
3634 EditorDOMPoint(aRangeToDelete
.EndRef()));
3635 if (NS_FAILED(rv
)) {
3637 "AutoDeleteRangesHandler::DeleteUnnecessaryNodesAndCollapseSelection() "
3641 return EditActionResult::HandledResult();
3644 nsresult
HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
3645 ComputeRangeToJoinBlockElementsInSameParent(
3646 const HTMLEditor
& aHTMLEditor
,
3647 nsIEditor::EDirection aDirectionAndAmount
, nsRange
& aRangeToDelete
,
3648 const Element
& aEditingHost
) const {
3649 MOZ_ASSERT(aHTMLEditor
.IsEditActionDataAvailable());
3650 MOZ_ASSERT(!aRangeToDelete
.Collapsed());
3651 MOZ_ASSERT(mMode
== Mode::JoinBlocksInSameParent
);
3652 MOZ_ASSERT(mLeftContent
);
3653 MOZ_ASSERT(mLeftContent
->IsElement());
3654 MOZ_ASSERT(aRangeToDelete
.GetStartContainer()->IsInclusiveDescendantOf(
3656 MOZ_ASSERT(mRightContent
);
3657 MOZ_ASSERT(mRightContent
->IsElement());
3659 aRangeToDelete
.GetEndContainer()->IsInclusiveDescendantOf(mRightContent
));
3660 MOZ_ASSERT(mLeftContent
->GetParentNode() == mRightContent
->GetParentNode());
3663 mDeleteRangesHandlerConst
.ComputeRangeToDeleteRangeWithTransaction(
3664 aHTMLEditor
, aDirectionAndAmount
, aRangeToDelete
, aEditingHost
);
3665 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
3666 "AutoDeleteRangesHandler::"
3667 "ComputeRangeToDeleteRangeWithTransaction() failed");
3671 Result
<EditActionResult
, nsresult
> HTMLEditor::AutoDeleteRangesHandler::
3672 AutoBlockElementsJoiner::JoinBlockElementsInSameParent(
3673 HTMLEditor
& aHTMLEditor
, nsIEditor::EDirection aDirectionAndAmount
,
3674 nsIEditor::EStripWrappers aStripWrappers
, nsRange
& aRangeToDelete
,
3675 SelectionWasCollapsed aSelectionWasCollapsed
,
3676 const Element
& aEditingHost
) {
3677 MOZ_ASSERT(aHTMLEditor
.IsEditActionDataAvailable());
3678 MOZ_ASSERT(!aRangeToDelete
.Collapsed());
3679 MOZ_ASSERT(mMode
== Mode::JoinBlocksInSameParent
);
3680 MOZ_ASSERT(mLeftContent
);
3681 MOZ_ASSERT(mLeftContent
->IsElement());
3682 MOZ_ASSERT(aRangeToDelete
.GetStartContainer()->IsInclusiveDescendantOf(
3684 MOZ_ASSERT(mRightContent
);
3685 MOZ_ASSERT(mRightContent
->IsElement());
3687 aRangeToDelete
.GetEndContainer()->IsInclusiveDescendantOf(mRightContent
));
3688 MOZ_ASSERT(mLeftContent
->GetParentNode() == mRightContent
->GetParentNode());
3690 const bool backspaceInRightBlock
=
3691 aSelectionWasCollapsed
== SelectionWasCollapsed::Yes
&&
3692 nsIEditor::DirectionIsBackspace(aDirectionAndAmount
);
3694 AutoRangeArray
rangesToDelete(aRangeToDelete
);
3695 Result
<CaretPoint
, nsresult
> caretPointOrError
=
3696 aHTMLEditor
.DeleteRangesWithTransaction(aDirectionAndAmount
,
3697 aStripWrappers
, rangesToDelete
);
3698 if (MOZ_UNLIKELY(caretPointOrError
.isErr())) {
3699 NS_WARNING("EditorBase::DeleteRangesWithTransaction() failed");
3700 return caretPointOrError
.propagateErr();
3703 nsresult rv
= caretPointOrError
.inspect().SuggestCaretPointTo(
3704 aHTMLEditor
, {SuggestCaret::OnlyIfHasSuggestion
,
3705 SuggestCaret::OnlyIfTransactionsAllowedToDoIt
,
3706 SuggestCaret::AndIgnoreTrivialError
});
3707 if (NS_FAILED(rv
)) {
3708 NS_WARNING("CaretPoint::SuggestCaretPointTo() failed");
3711 NS_WARNING_ASSERTION(rv
!= NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR
,
3712 "CaretPoint::SuggestCaretPointTo() failed, but ignored");
3714 if (NS_WARN_IF(!mLeftContent
->GetParentNode()) ||
3715 NS_WARN_IF(!mRightContent
->GetParentNode()) ||
3716 NS_WARN_IF(mLeftContent
->GetParentNode() !=
3717 mRightContent
->GetParentNode())) {
3718 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
);
3721 auto startOfRightContent
=
3722 HTMLEditUtils::GetDeepestEditableStartPointOf
<EditorDOMPoint
>(
3724 AutoTrackDOMPoint
trackStartOfRightContent(aHTMLEditor
.RangeUpdaterRef(),
3725 &startOfRightContent
);
3726 Result
<EditorDOMPoint
, nsresult
> atFirstChildOfTheLastRightNodeOrError
=
3727 JoinNodesDeepWithTransaction(aHTMLEditor
, MOZ_KnownLive(*mLeftContent
),
3728 MOZ_KnownLive(*mRightContent
));
3729 if (MOZ_UNLIKELY(atFirstChildOfTheLastRightNodeOrError
.isErr())) {
3730 NS_WARNING("HTMLEditor::JoinNodesDeepWithTransaction() failed");
3731 return atFirstChildOfTheLastRightNodeOrError
.propagateErr();
3733 MOZ_ASSERT(atFirstChildOfTheLastRightNodeOrError
.inspect().IsSet());
3734 trackStartOfRightContent
.FlushAndStopTracking();
3735 if (NS_WARN_IF(!startOfRightContent
.IsSet()) ||
3736 NS_WARN_IF(!startOfRightContent
.GetContainer()->IsInComposedDoc())) {
3737 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
);
3740 // If we're deleting selection (not replacing with new content) and the joined
3741 // point follows a text node, we should put caret to end of the preceding text
3742 // node because the other browsers insert following inputs into there.
3743 if (MayEditActionDeleteAroundCollapsedSelection(
3744 aHTMLEditor
.GetEditAction())) {
3745 WSRunScanner
scanner(&aEditingHost
, startOfRightContent
,
3746 BlockInlineCheck::UseComputedDisplayOutsideStyle
);
3747 WSScanResult maybePreviousText
=
3748 scanner
.ScanPreviousVisibleNodeOrBlockBoundaryFrom(startOfRightContent
);
3749 if (maybePreviousText
.IsContentEditable() &&
3750 maybePreviousText
.InVisibleOrCollapsibleCharacters()) {
3751 nsresult rv
= aHTMLEditor
.CollapseSelectionTo(
3752 maybePreviousText
.Point
<EditorRawDOMPoint
>());
3753 if (NS_FAILED(rv
)) {
3754 NS_WARNING("EditorBase::CollapseSelectionTo() failed");
3757 // If we prefer to use style in the previous line, we should forget
3758 // previous styles since the caret position has all styles which we want
3759 // to use with new content.
3760 if (backspaceInRightBlock
) {
3761 aHTMLEditor
.TopLevelEditSubActionDataRef()
3762 .mCachedPendingStyles
->Clear();
3764 // And we don't want to keep extending a link at ex-end of the previous
3766 if (HTMLEditor::GetLinkElement(maybePreviousText
.TextPtr())) {
3767 aHTMLEditor
.mPendingStylesToApplyToNewContent
3768 ->ClearLinkAndItsSpecifiedStyle();
3770 return EditActionResult::HandledResult();
3774 // Otherwise, we should put caret at start of the right content.
3775 rv
= aHTMLEditor
.CollapseSelectionTo(
3776 atFirstChildOfTheLastRightNodeOrError
.inspect());
3777 if (NS_FAILED(rv
)) {
3778 NS_WARNING("EditorBase::CollapseSelectionTo() failed");
3781 return EditActionResult::HandledResult();
3784 Result
<bool, nsresult
>
3785 HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
3786 ComputeRangeToDeleteNodesEntirelyInRangeButKeepTableStructure(
3787 const HTMLEditor
& aHTMLEditor
, nsRange
& aRange
,
3788 AutoDeleteRangesHandler::SelectionWasCollapsed aSelectionWasCollapsed
)
3790 MOZ_ASSERT(aHTMLEditor
.IsEditActionDataAvailable());
3792 AutoTArray
<OwningNonNull
<nsIContent
>, 10> arrayOfTopChildren
;
3793 DOMSubtreeIterator iter
;
3794 nsresult rv
= iter
.Init(aRange
);
3795 if (NS_FAILED(rv
)) {
3796 NS_WARNING("DOMSubtreeIterator::Init() failed");
3799 iter
.AppendAllNodesToArray(arrayOfTopChildren
);
3800 return NeedsToJoinNodesAfterDeleteNodesEntirelyInRangeButKeepTableStructure(
3801 aHTMLEditor
, arrayOfTopChildren
, aSelectionWasCollapsed
);
3804 Result
<bool, nsresult
> HTMLEditor::AutoDeleteRangesHandler::
3805 AutoBlockElementsJoiner::DeleteNodesEntirelyInRangeButKeepTableStructure(
3806 HTMLEditor
& aHTMLEditor
, nsRange
& aRange
,
3807 AutoDeleteRangesHandler::SelectionWasCollapsed aSelectionWasCollapsed
) {
3808 MOZ_ASSERT(aHTMLEditor
.IsEditActionDataAvailable());
3810 // Build a list of direct child nodes in the range
3811 AutoTArray
<OwningNonNull
<nsIContent
>, 10> arrayOfTopChildren
;
3812 DOMSubtreeIterator iter
;
3813 nsresult rv
= iter
.Init(aRange
);
3814 if (NS_FAILED(rv
)) {
3815 NS_WARNING("DOMSubtreeIterator::Init() failed");
3818 iter
.AppendAllNodesToArray(arrayOfTopChildren
);
3820 // Now that we have the list, delete non-table elements
3821 bool needsToJoinLater
=
3822 NeedsToJoinNodesAfterDeleteNodesEntirelyInRangeButKeepTableStructure(
3823 aHTMLEditor
, arrayOfTopChildren
, aSelectionWasCollapsed
);
3824 for (auto& content
: arrayOfTopChildren
) {
3825 // XXX After here, the child contents in the array may have been moved
3826 // to somewhere or removed. We should handle it.
3828 // MOZ_KnownLive because 'arrayOfTopChildren' is guaranteed to
3831 // Even with https://bugzilla.mozilla.org/show_bug.cgi?id=1620312 fixed
3832 // this might need to stay, because 'arrayOfTopChildren' is not const,
3833 // so it's not obvious how to prove via static analysis that it won't
3834 // change and release us.
3836 DeleteContentButKeepTableStructure(aHTMLEditor
, MOZ_KnownLive(content
));
3837 if (NS_WARN_IF(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
3838 return Err(NS_ERROR_EDITOR_DESTROYED
);
3840 NS_WARNING_ASSERTION(
3842 "AutoBlockElementsJoiner::DeleteContentButKeepTableStructure() failed, "
3845 return needsToJoinLater
;
3848 bool HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
3849 NeedsToJoinNodesAfterDeleteNodesEntirelyInRangeButKeepTableStructure(
3850 const HTMLEditor
& aHTMLEditor
,
3851 const nsTArray
<OwningNonNull
<nsIContent
>>& aArrayOfContents
,
3852 AutoDeleteRangesHandler::SelectionWasCollapsed aSelectionWasCollapsed
)
3854 // If original selection was collapsed, we need always to join the nodes.
3856 if (aSelectionWasCollapsed
==
3857 AutoDeleteRangesHandler::SelectionWasCollapsed::No
) {
3860 // If something visible is deleted, no need to join. Visible means
3861 // all nodes except non-visible textnodes and breaks.
3862 if (aArrayOfContents
.IsEmpty()) {
3865 for (const OwningNonNull
<nsIContent
>& content
: aArrayOfContents
) {
3866 if (content
->IsText()) {
3867 if (HTMLEditUtils::IsInVisibleTextFrames(aHTMLEditor
.GetPresContext(),
3868 *content
->AsText())) {
3873 // XXX If it's an element node, we should check whether it has visible
3875 if (!content
->IsElement() ||
3876 HTMLEditUtils::IsEmptyNode(
3877 *content
->AsElement(),
3878 {EmptyCheckOption::TreatSingleBRElementAsVisible
,
3879 EmptyCheckOption::TreatNonEditableContentAsInvisible
})) {
3882 if (!HTMLEditUtils::IsInvisibleBRElement(*content
)) {
3889 nsresult
HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
3890 DeleteTextAtStartAndEndOfRange(HTMLEditor
& aHTMLEditor
, nsRange
& aRange
) {
3891 EditorDOMPoint
rangeStart(aRange
.StartRef());
3892 EditorDOMPoint
rangeEnd(aRange
.EndRef());
3893 if (rangeStart
.IsInTextNode() && !rangeStart
.IsEndOfContainer()) {
3894 // Delete to last character
3895 OwningNonNull
<Text
> textNode
= *rangeStart
.ContainerAs
<Text
>();
3896 Result
<CaretPoint
, nsresult
> caretPointOrError
=
3897 aHTMLEditor
.DeleteTextWithTransaction(
3898 textNode
, rangeStart
.Offset(),
3899 rangeStart
.GetContainer()->Length() - rangeStart
.Offset());
3900 if (MOZ_UNLIKELY(caretPointOrError
.isErr())) {
3901 NS_WARNING("HTMLEditor::DeleteTextWithTransaction() failed");
3902 return caretPointOrError
.unwrapErr();
3904 nsresult rv
= caretPointOrError
.inspect().SuggestCaretPointTo(
3905 aHTMLEditor
, {SuggestCaret::OnlyIfHasSuggestion
,
3906 SuggestCaret::OnlyIfTransactionsAllowedToDoIt
,
3907 SuggestCaret::AndIgnoreTrivialError
});
3908 if (NS_FAILED(rv
)) {
3909 NS_WARNING("CaretPoint::SuggestCaretPointTo() failed");
3912 NS_WARNING_ASSERTION(
3913 rv
!= NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR
,
3914 "CaretPoint::SuggestCaretPointTo() failed, but ignored");
3916 if (rangeEnd
.IsInTextNode() && !rangeEnd
.IsStartOfContainer()) {
3917 // Delete to first character
3918 OwningNonNull
<Text
> textNode
= *rangeEnd
.ContainerAs
<Text
>();
3919 Result
<CaretPoint
, nsresult
> caretPointOrError
=
3920 aHTMLEditor
.DeleteTextWithTransaction(textNode
, 0, rangeEnd
.Offset());
3921 if (MOZ_UNLIKELY(caretPointOrError
.isErr())) {
3922 NS_WARNING("HTMLEditor::DeleteTextWithTransaction() failed");
3923 return caretPointOrError
.unwrapErr();
3925 nsresult rv
= caretPointOrError
.inspect().SuggestCaretPointTo(
3926 aHTMLEditor
, {SuggestCaret::OnlyIfHasSuggestion
,
3927 SuggestCaret::OnlyIfTransactionsAllowedToDoIt
,
3928 SuggestCaret::AndIgnoreTrivialError
});
3929 if (NS_FAILED(rv
)) {
3930 NS_WARNING("CaretPoint::SuggestCaretPointTo() failed");
3933 NS_WARNING_ASSERTION(
3934 rv
!= NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR
,
3935 "CaretPoint::SuggestCaretPointTo() failed, but ignored");
3940 nsresult
HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
3941 ComputeRangeToDeleteNonCollapsedRange(
3942 const HTMLEditor
& aHTMLEditor
,
3943 nsIEditor::EDirection aDirectionAndAmount
, nsRange
& aRangeToDelete
,
3944 AutoDeleteRangesHandler::SelectionWasCollapsed aSelectionWasCollapsed
,
3945 const Element
& aEditingHost
) const {
3946 MOZ_ASSERT(aHTMLEditor
.IsEditActionDataAvailable());
3947 MOZ_ASSERT(!aRangeToDelete
.Collapsed());
3948 MOZ_ASSERT(mLeftContent
);
3949 MOZ_ASSERT(mLeftContent
->IsElement());
3950 MOZ_ASSERT(aRangeToDelete
.GetStartContainer()->IsInclusiveDescendantOf(
3952 MOZ_ASSERT(mRightContent
);
3953 MOZ_ASSERT(mRightContent
->IsElement());
3955 aRangeToDelete
.GetEndContainer()->IsInclusiveDescendantOf(mRightContent
));
3957 Result
<bool, nsresult
> result
=
3958 ComputeRangeToDeleteNodesEntirelyInRangeButKeepTableStructure(
3959 aHTMLEditor
, aRangeToDelete
, aSelectionWasCollapsed
);
3960 if (result
.isErr()) {
3962 "AutoBlockElementsJoiner::"
3963 "ComputeRangeToDeleteNodesEntirelyInRangeButKeepTableStructure() "
3965 return result
.unwrapErr();
3967 if (!result
.unwrap()) {
3971 AutoInclusiveAncestorBlockElementsJoiner
joiner(*mLeftContent
,
3973 Result
<bool, nsresult
> canJoinThem
=
3974 joiner
.Prepare(aHTMLEditor
, aEditingHost
);
3975 if (canJoinThem
.isErr()) {
3976 NS_WARNING("AutoInclusiveAncestorBlockElementsJoiner::Prepare() failed");
3977 return canJoinThem
.unwrapErr();
3980 if (!canJoinThem
.unwrap()) {
3981 return NS_SUCCESS_DOM_NO_OPERATION
;
3984 if (!joiner
.CanJoinBlocks()) {
3988 nsresult rv
= joiner
.ComputeRangeToDelete(aHTMLEditor
, EditorDOMPoint(),
3990 NS_WARNING_ASSERTION(
3992 "AutoInclusiveAncestorBlockElementsJoiner::ComputeRangeToDelete() "
3997 Result
<EditActionResult
, nsresult
> HTMLEditor::AutoDeleteRangesHandler::
3998 AutoBlockElementsJoiner::HandleDeleteNonCollapsedRange(
3999 HTMLEditor
& aHTMLEditor
, nsIEditor::EDirection aDirectionAndAmount
,
4000 nsIEditor::EStripWrappers aStripWrappers
, nsRange
& aRangeToDelete
,
4001 AutoDeleteRangesHandler::SelectionWasCollapsed aSelectionWasCollapsed
,
4002 const Element
& aEditingHost
) {
4003 MOZ_ASSERT(aHTMLEditor
.IsEditActionDataAvailable());
4004 MOZ_ASSERT(!aRangeToDelete
.Collapsed());
4005 MOZ_ASSERT(mDeleteRangesHandler
);
4006 MOZ_ASSERT(mLeftContent
);
4007 MOZ_ASSERT(mLeftContent
->IsElement());
4008 MOZ_ASSERT(aRangeToDelete
.GetStartContainer()->IsInclusiveDescendantOf(
4010 MOZ_ASSERT(mRightContent
);
4011 MOZ_ASSERT(mRightContent
->IsElement());
4013 aRangeToDelete
.GetEndContainer()->IsInclusiveDescendantOf(mRightContent
));
4015 const bool backspaceInRightBlock
=
4016 aSelectionWasCollapsed
== SelectionWasCollapsed::Yes
&&
4017 nsIEditor::DirectionIsBackspace(aDirectionAndAmount
);
4019 // Otherwise, delete every nodes in the range, then, clean up something.
4020 EditActionResult result
= EditActionResult::IgnoredResult();
4021 EditorDOMPoint pointToPutCaret
;
4023 OwningNonNull
<nsRange
> rangeToDelete(aRangeToDelete
);
4024 AutoTrackDOMRange
firstRangeTracker(aHTMLEditor
.RangeUpdaterRef(),
4027 Result
<bool, nsresult
> deleteResult
=
4028 DeleteNodesEntirelyInRangeButKeepTableStructure(
4029 aHTMLEditor
, rangeToDelete
, aSelectionWasCollapsed
);
4030 if (MOZ_UNLIKELY(deleteResult
.isErr())) {
4032 "AutoBlockElementsJoiner::"
4033 "DeleteNodesEntirelyInRangeButKeepTableStructure() failed");
4034 return deleteResult
.propagateErr();
4037 const bool joinInclusiveAncestorBlockElements
= deleteResult
.unwrap();
4039 // Check endpoints for possible text deletion. We can assume that if
4040 // text node is found, we can delete to end or to beginning as
4041 // appropriate, since the case where both sel endpoints in same text
4042 // node was already handled (we wouldn't be here)
4043 nsresult rv
= DeleteTextAtStartAndEndOfRange(aHTMLEditor
, rangeToDelete
);
4044 if (NS_FAILED(rv
)) {
4046 "AutoBlockElementsJoiner::DeleteTextAtStartAndEndOfRange() failed");
4050 if (!joinInclusiveAncestorBlockElements
) {
4054 AutoInclusiveAncestorBlockElementsJoiner
joiner(*mLeftContent
,
4056 Result
<bool, nsresult
> canJoinThem
=
4057 joiner
.Prepare(aHTMLEditor
, aEditingHost
);
4058 if (canJoinThem
.isErr()) {
4059 NS_WARNING("AutoInclusiveAncestorBlockElementsJoiner::Prepare() failed");
4060 return canJoinThem
.propagateErr();
4063 // If we're joining blocks: if deleting forward the selection should
4064 // be collapsed to the end of the selection, if deleting backward the
4065 // selection should be collapsed to the beginning of the selection.
4066 // But if we're not joining then the selection should collapse to the
4067 // beginning of the selection if we're deleting forward, because the
4068 // end of the selection will still be in the next block. And same
4069 // thing for deleting backwards (selection should collapse to the end,
4070 // because the beginning will still be in the first block). See Bug
4072 if (aDirectionAndAmount
== nsIEditor::eNext
) {
4073 aDirectionAndAmount
= nsIEditor::ePrevious
;
4075 aDirectionAndAmount
= nsIEditor::eNext
;
4078 if (!canJoinThem
.inspect()) {
4079 result
.MarkAsCanceled();
4083 if (!joiner
.CanJoinBlocks()) {
4087 Result
<EditActionResult
, nsresult
> joinResult
=
4088 joiner
.Run(aHTMLEditor
, aEditingHost
);
4089 if (MOZ_UNLIKELY(joinResult
.isErr())) {
4090 NS_WARNING("AutoInclusiveAncestorBlockElementsJoiner::Run() failed");
4093 result
|= joinResult
.unwrap();
4095 if (joiner
.ShouldDeleteLeafContentInstead()) {
4096 NS_ASSERTION(result
.Ignored(),
4097 "Assumed `AutoInclusiveAncestorBlockElementsJoiner::Run()` "
4098 "returning ignored, but returned not ignored");
4100 NS_ASSERTION(!result
.Ignored(),
4101 "Assumed `AutoInclusiveAncestorBlockElementsJoiner::Run()` "
4102 "returning handled, but returned ignored");
4104 #endif // #ifdef DEBUG
4105 pointToPutCaret
= joiner
.PointRefToPutCaret();
4109 // If we're deleting selection (not replacing with new content) and
4110 // AutoInclusiveAncestorBlockElementsJoiner computed new caret position, we
4111 // should use it. Otherwise, we should keep the traditional behavior.
4112 if (result
.Handled() && pointToPutCaret
.IsSet()) {
4113 EditorDOMRange
range(aRangeToDelete
);
4115 mDeleteRangesHandler
->DeleteUnnecessaryNodes(aHTMLEditor
, range
);
4116 if (NS_FAILED(rv
)) {
4117 NS_WARNING("AutoDeleteRangesHandler::DeleteUnnecessaryNodes() failed");
4120 rv
= aHTMLEditor
.CollapseSelectionTo(pointToPutCaret
);
4121 if (NS_FAILED(rv
)) {
4122 NS_WARNING("EditorBase::CollapseSelectionTo() failed");
4125 // If we prefer to use style in the previous line, we should forget
4126 // previous styles since the caret position has all styles which we want
4127 // to use with new content.
4128 if (backspaceInRightBlock
) {
4129 aHTMLEditor
.TopLevelEditSubActionDataRef().mCachedPendingStyles
->Clear();
4131 // And we don't want to keep extending a link at ex-end of the previous
4133 if (HTMLEditor::GetLinkElement(pointToPutCaret
.GetContainer())) {
4134 aHTMLEditor
.mPendingStylesToApplyToNewContent
4135 ->ClearLinkAndItsSpecifiedStyle();
4141 mDeleteRangesHandler
->DeleteUnnecessaryNodesAndCollapseSelection(
4142 aHTMLEditor
, aDirectionAndAmount
,
4143 EditorDOMPoint(aRangeToDelete
.StartRef()),
4144 EditorDOMPoint(aRangeToDelete
.EndRef()));
4145 if (NS_FAILED(rv
)) {
4147 "AutoDeleteRangesHandler::DeleteUnnecessaryNodesAndCollapseSelection() "
4152 result
.MarkAsHandled();
4156 nsresult
HTMLEditor::AutoDeleteRangesHandler::DeleteUnnecessaryNodes(
4157 HTMLEditor
& aHTMLEditor
, EditorDOMRange
& aRange
) {
4158 MOZ_ASSERT(aHTMLEditor
.IsTopLevelEditSubActionDataAvailable());
4159 MOZ_ASSERT(EditorUtils::IsEditableContent(
4160 *aRange
.StartRef().ContainerAs
<nsIContent
>(), EditorType::HTML
));
4161 MOZ_ASSERT(EditorUtils::IsEditableContent(
4162 *aRange
.EndRef().ContainerAs
<nsIContent
>(), EditorType::HTML
));
4164 // If we're handling DnD, this is called to delete dragging item from the
4165 // tree. In this case, we should remove parent blocks if it becomes empty.
4166 if (aHTMLEditor
.GetEditAction() == EditAction::eDrop
||
4167 aHTMLEditor
.GetEditAction() == EditAction::eDeleteByDrag
) {
4168 MOZ_ASSERT(aRange
.Collapsed() ||
4169 (aRange
.StartRef().GetContainer()->GetNextSibling() ==
4170 aRange
.EndRef().GetContainer() &&
4171 aRange
.StartRef().IsEndOfContainer() &&
4172 aRange
.EndRef().IsStartOfContainer()));
4173 AutoTrackDOMRange
trackRange(aHTMLEditor
.RangeUpdaterRef(), &aRange
);
4175 nsresult rv
= DeleteParentBlocksWithTransactionIfEmpty(aHTMLEditor
,
4177 if (NS_FAILED(rv
)) {
4179 "HTMLEditor::DeleteParentBlocksWithTransactionIfEmpty() failed");
4182 aHTMLEditor
.TopLevelEditSubActionDataRef().mDidDeleteEmptyParentBlocks
=
4184 // If we removed parent blocks, Selection should be collapsed at where
4185 // the most ancestor empty block has been.
4186 if (aHTMLEditor
.TopLevelEditSubActionDataRef()
4187 .mDidDeleteEmptyParentBlocks
) {
4192 if (NS_WARN_IF(!aRange
.IsInContentNodes()) ||
4193 NS_WARN_IF(!EditorUtils::IsEditableContent(
4194 *aRange
.StartRef().ContainerAs
<nsIContent
>(), EditorType::HTML
)) ||
4195 NS_WARN_IF(!EditorUtils::IsEditableContent(
4196 *aRange
.EndRef().ContainerAs
<nsIContent
>(), EditorType::HTML
))) {
4197 return NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
;
4200 // We might have left only collapsed white-space in the start/end nodes
4201 AutoTrackDOMRange
trackRange(aHTMLEditor
.RangeUpdaterRef(), &aRange
);
4203 OwningNonNull
<nsIContent
> startContainer
=
4204 *aRange
.StartRef().ContainerAs
<nsIContent
>();
4205 OwningNonNull
<nsIContent
> endContainer
=
4206 *aRange
.EndRef().ContainerAs
<nsIContent
>();
4208 DeleteNodeIfInvisibleAndEditableTextNode(aHTMLEditor
, startContainer
);
4209 if (NS_WARN_IF(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
4210 return NS_ERROR_EDITOR_DESTROYED
;
4212 NS_WARNING_ASSERTION(
4214 "AutoDeleteRangesHandler::DeleteNodeIfInvisibleAndEditableTextNode() "
4215 "failed to remove start node, but ignored");
4216 // If we've not handled the selection end container, and it's still
4217 // editable, let's handle it.
4218 if (aRange
.InSameContainer() ||
4219 !EditorUtils::IsEditableContent(
4220 *aRange
.EndRef().ContainerAs
<nsIContent
>(), EditorType::HTML
)) {
4223 rv
= DeleteNodeIfInvisibleAndEditableTextNode(aHTMLEditor
, endContainer
);
4224 if (NS_WARN_IF(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
4225 return NS_ERROR_EDITOR_DESTROYED
;
4227 NS_WARNING_ASSERTION(
4229 "AutoDeleteRangesHandler::DeleteNodeIfInvisibleAndEditableTextNode() "
4230 "failed to remove end node, but ignored");
4235 HTMLEditor::AutoDeleteRangesHandler::DeleteUnnecessaryNodesAndCollapseSelection(
4236 HTMLEditor
& aHTMLEditor
, nsIEditor::EDirection aDirectionAndAmount
,
4237 const EditorDOMPoint
& aSelectionStartPoint
,
4238 const EditorDOMPoint
& aSelectionEndPoint
) {
4239 EditorDOMRange
range(aSelectionStartPoint
, aSelectionEndPoint
);
4240 nsresult rv
= DeleteUnnecessaryNodes(aHTMLEditor
, range
);
4241 if (NS_FAILED(rv
)) {
4242 NS_WARNING("AutoDeleteRangesHandler::DeleteUnnecessaryNodes() failed");
4246 if (aHTMLEditor
.GetEditAction() == EditAction::eDrop
||
4247 aHTMLEditor
.GetEditAction() == EditAction::eDeleteByDrag
) {
4248 // If we removed parent blocks, Selection should be collapsed at where
4249 // the most ancestor empty block has been.
4250 // XXX I think that if the range is not in active editing host, we should
4251 // not try to collapse selection here.
4252 if (aHTMLEditor
.TopLevelEditSubActionDataRef()
4253 .mDidDeleteEmptyParentBlocks
) {
4254 nsresult rv
= aHTMLEditor
.CollapseSelectionTo(range
.StartRef());
4255 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
4256 "EditorBase::CollapseSelectionTo() failed");
4261 rv
= aHTMLEditor
.CollapseSelectionTo(
4262 aDirectionAndAmount
== nsIEditor::ePrevious
? range
.EndRef()
4263 : range
.StartRef());
4264 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
4265 "EditorBase::CollapseSelectionTo() failed");
4270 HTMLEditor::AutoDeleteRangesHandler::DeleteNodeIfInvisibleAndEditableTextNode(
4271 HTMLEditor
& aHTMLEditor
, nsIContent
& aContent
) {
4272 MOZ_ASSERT(aHTMLEditor
.IsEditActionDataAvailable());
4274 Text
* text
= aContent
.GetAsText();
4279 if (!HTMLEditUtils::IsRemovableFromParentNode(*text
) ||
4280 HTMLEditUtils::IsVisibleTextNode(*text
)) {
4284 nsresult rv
= aHTMLEditor
.DeleteNodeWithTransaction(aContent
);
4285 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
4286 "EditorBase::DeleteNodeWithTransaction() failed");
4291 HTMLEditor::AutoDeleteRangesHandler::DeleteParentBlocksWithTransactionIfEmpty(
4292 HTMLEditor
& aHTMLEditor
, const EditorDOMPoint
& aPoint
) {
4293 MOZ_ASSERT(aPoint
.IsSet());
4294 MOZ_ASSERT(aHTMLEditor
.mPlaceholderBatch
);
4296 // First, check there is visible contents before the point in current block.
4297 RefPtr
<Element
> editingHost
= aHTMLEditor
.ComputeEditingHost();
4298 WSRunScanner
wsScannerForPoint(
4299 editingHost
, aPoint
, BlockInlineCheck::UseComputedDisplayOutsideStyle
);
4300 if (!wsScannerForPoint
.StartsFromCurrentBlockBoundary()) {
4301 // If there is visible node before the point, we shouldn't remove the
4303 return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND
;
4305 if (NS_WARN_IF(!wsScannerForPoint
.GetStartReasonContent()) ||
4306 NS_WARN_IF(!wsScannerForPoint
.GetStartReasonContent()->GetParentNode())) {
4307 return NS_ERROR_FAILURE
;
4309 if (editingHost
== wsScannerForPoint
.GetStartReasonContent()) {
4310 // If we reach editing host, there is no parent blocks which can be removed.
4311 return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND
;
4313 if (HTMLEditUtils::IsTableCellOrCaption(
4314 *wsScannerForPoint
.GetStartReasonContent())) {
4315 // If we reach a <td>, <th> or <caption>, we shouldn't remove it even
4316 // becomes empty because removing such element changes the structure of
4318 return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND
;
4321 // Next, check there is visible contents after the point in current block.
4322 WSScanResult forwardScanFromPointResult
=
4323 wsScannerForPoint
.ScanNextVisibleNodeOrBlockBoundaryFrom(aPoint
);
4324 if (forwardScanFromPointResult
.Failed()) {
4325 NS_WARNING("WSRunScanner::ScanNextVisibleNodeOrBlockBoundaryFrom() failed");
4326 return NS_ERROR_FAILURE
;
4328 if (forwardScanFromPointResult
.ReachedBRElement()) {
4329 // XXX In my understanding, this is odd. The end reason may not be
4330 // same as the reached <br> element because the equality is
4331 // guaranteed only when ReachedCurrentBlockBoundary() returns true.
4332 // However, looks like that this code assumes that
4333 // GetEndReasonContent() returns the (or a) <br> element.
4334 NS_ASSERTION(wsScannerForPoint
.GetEndReasonContent() ==
4335 forwardScanFromPointResult
.BRElementPtr(),
4336 "End reason is not the reached <br> element");
4337 // If the <br> element is visible, we shouldn't remove the parent block.
4338 if (HTMLEditUtils::IsVisibleBRElement(
4339 *wsScannerForPoint
.GetEndReasonContent())) {
4340 return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND
;
4342 if (wsScannerForPoint
.GetEndReasonContent()->GetNextSibling()) {
4343 WSScanResult scanResult
=
4344 WSRunScanner::ScanNextVisibleNodeOrBlockBoundary(
4346 EditorRawDOMPoint::After(
4347 *wsScannerForPoint
.GetEndReasonContent()),
4348 BlockInlineCheck::UseComputedDisplayOutsideStyle
);
4349 if (scanResult
.Failed()) {
4350 NS_WARNING("WSRunScanner::ScanNextVisibleNodeOrBlockBoundary() failed");
4351 return NS_ERROR_FAILURE
;
4353 if (!scanResult
.ReachedCurrentBlockBoundary()) {
4354 // If we couldn't reach the block's end after the invisible <br>,
4355 // that means that there is visible content.
4356 return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND
;
4359 } else if (!forwardScanFromPointResult
.ReachedCurrentBlockBoundary()) {
4360 // If we couldn't reach the block's end, the block has visible content.
4361 return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND
;
4364 // Delete the parent block.
4365 EditorDOMPoint
nextPoint(
4366 wsScannerForPoint
.GetStartReasonContent()->GetParentNode(), 0);
4367 nsresult rv
= aHTMLEditor
.DeleteNodeWithTransaction(
4368 MOZ_KnownLive(*wsScannerForPoint
.GetStartReasonContent()));
4369 if (NS_FAILED(rv
)) {
4370 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
4373 // If we reach editing host, return NS_OK.
4374 if (nextPoint
.GetContainer() == editingHost
) {
4378 // Otherwise, we need to check whether we're still in empty block or not.
4380 // If we have mutation event listeners, the next point is now outside of
4381 // editing host or editing hos has been changed.
4382 if (aHTMLEditor
.MayHaveMutationEventListeners(
4383 NS_EVENT_BITS_MUTATION_NODEREMOVED
|
4384 NS_EVENT_BITS_MUTATION_NODEREMOVEDFROMDOCUMENT
|
4385 NS_EVENT_BITS_MUTATION_SUBTREEMODIFIED
)) {
4386 Element
* newEditingHost
= aHTMLEditor
.ComputeEditingHost();
4387 if (NS_WARN_IF(!newEditingHost
) ||
4388 NS_WARN_IF(newEditingHost
!= editingHost
)) {
4389 return NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
;
4391 if (NS_WARN_IF(!EditorUtils::IsDescendantOf(*nextPoint
.GetContainer(),
4392 *newEditingHost
))) {
4393 return NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
;
4397 rv
= DeleteParentBlocksWithTransactionIfEmpty(aHTMLEditor
, nextPoint
);
4398 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
4399 "AutoDeleteRangesHandler::"
4400 "DeleteParentBlocksWithTransactionIfEmpty() failed");
4405 HTMLEditor::AutoDeleteRangesHandler::ComputeRangeToDeleteRangeWithTransaction(
4406 const HTMLEditor
& aHTMLEditor
, nsIEditor::EDirection aDirectionAndAmount
,
4407 nsRange
& aRangeToDelete
, const Element
& aEditingHost
) const {
4408 MOZ_ASSERT(aHTMLEditor
.IsEditActionDataAvailable());
4410 const EditorBase::HowToHandleCollapsedRange howToHandleCollapsedRange
=
4411 EditorBase::HowToHandleCollapsedRangeFor(aDirectionAndAmount
);
4412 if (MOZ_UNLIKELY(aRangeToDelete
.Collapsed() &&
4413 howToHandleCollapsedRange
==
4414 EditorBase::HowToHandleCollapsedRange::Ignore
)) {
4415 return NS_SUCCESS_DOM_NO_OPERATION
;
4418 // If it's not collapsed, `DeleteRangeTransaction::Create()` will be called
4419 // with it and `DeleteRangeTransaction` won't modify the range.
4420 if (!aRangeToDelete
.Collapsed()) {
4424 const auto ExtendRangeToSelectCharacterForward
=
4425 [](nsRange
& aRange
, const EditorRawDOMPointInText
& aCaretPoint
) -> void {
4426 const nsTextFragment
& textFragment
=
4427 aCaretPoint
.ContainerAs
<Text
>()->TextFragment();
4428 if (!textFragment
.GetLength()) {
4431 if (textFragment
.IsHighSurrogateFollowedByLowSurrogateAt(
4432 aCaretPoint
.Offset())) {
4433 DebugOnly
<nsresult
> rvIgnored
= aRange
.SetStartAndEnd(
4434 aCaretPoint
.ContainerAs
<Text
>(), aCaretPoint
.Offset(),
4435 aCaretPoint
.ContainerAs
<Text
>(), aCaretPoint
.Offset() + 2);
4436 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored
),
4437 "nsRange::SetStartAndEnd() failed");
4440 DebugOnly
<nsresult
> rvIgnored
= aRange
.SetStartAndEnd(
4441 aCaretPoint
.ContainerAs
<Text
>(), aCaretPoint
.Offset(),
4442 aCaretPoint
.ContainerAs
<Text
>(), aCaretPoint
.Offset() + 1);
4443 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored
),
4444 "nsRange::SetStartAndEnd() failed");
4446 const auto ExtendRangeToSelectCharacterBackward
=
4447 [](nsRange
& aRange
, const EditorRawDOMPointInText
& aCaretPoint
) -> void {
4448 if (aCaretPoint
.IsStartOfContainer()) {
4451 const nsTextFragment
& textFragment
=
4452 aCaretPoint
.ContainerAs
<Text
>()->TextFragment();
4453 if (!textFragment
.GetLength()) {
4456 if (textFragment
.IsLowSurrogateFollowingHighSurrogateAt(
4457 aCaretPoint
.Offset() - 1)) {
4458 DebugOnly
<nsresult
> rvIgnored
= aRange
.SetStartAndEnd(
4459 aCaretPoint
.ContainerAs
<Text
>(), aCaretPoint
.Offset() - 2,
4460 aCaretPoint
.ContainerAs
<Text
>(), aCaretPoint
.Offset());
4461 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored
),
4462 "nsRange::SetStartAndEnd() failed");
4465 DebugOnly
<nsresult
> rvIgnored
= aRange
.SetStartAndEnd(
4466 aCaretPoint
.ContainerAs
<Text
>(), aCaretPoint
.Offset() - 1,
4467 aCaretPoint
.ContainerAs
<Text
>(), aCaretPoint
.Offset());
4468 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored
),
4469 "nsRange::SetStartAndEnd() failed");
4472 // In the other cases, `EditorBase::CreateTransactionForCollapsedRange()`
4473 // will handle the collapsed range.
4474 EditorRawDOMPoint
caretPoint(aRangeToDelete
.StartRef());
4475 if (howToHandleCollapsedRange
==
4476 EditorBase::HowToHandleCollapsedRange::ExtendBackward
&&
4477 caretPoint
.IsStartOfContainer()) {
4478 nsIContent
* previousEditableContent
= HTMLEditUtils::GetPreviousContent(
4479 *caretPoint
.GetContainer(), {WalkTreeOption::IgnoreNonEditableNode
},
4480 BlockInlineCheck::Unused
, &aEditingHost
);
4481 if (!previousEditableContent
) {
4484 if (!previousEditableContent
->IsText()) {
4485 IgnoredErrorResult ignoredError
;
4486 aRangeToDelete
.SelectNode(*previousEditableContent
, ignoredError
);
4487 NS_WARNING_ASSERTION(!ignoredError
.Failed(),
4488 "nsRange::SelectNode() failed");
4492 ExtendRangeToSelectCharacterBackward(
4494 EditorRawDOMPointInText::AtEndOf(*previousEditableContent
->AsText()));
4498 if (howToHandleCollapsedRange
==
4499 EditorBase::HowToHandleCollapsedRange::ExtendForward
&&
4500 caretPoint
.IsEndOfContainer()) {
4501 nsIContent
* nextEditableContent
= HTMLEditUtils::GetNextContent(
4502 *caretPoint
.GetContainer(), {WalkTreeOption::IgnoreNonEditableNode
},
4503 BlockInlineCheck::Unused
, &aEditingHost
);
4504 if (!nextEditableContent
) {
4508 if (!nextEditableContent
->IsText()) {
4509 IgnoredErrorResult ignoredError
;
4510 aRangeToDelete
.SelectNode(*nextEditableContent
, ignoredError
);
4511 NS_WARNING_ASSERTION(!ignoredError
.Failed(),
4512 "nsRange::SelectNode() failed");
4516 ExtendRangeToSelectCharacterForward(
4518 EditorRawDOMPointInText(nextEditableContent
->AsText(), 0));
4522 if (caretPoint
.IsInTextNode()) {
4523 if (howToHandleCollapsedRange
==
4524 EditorBase::HowToHandleCollapsedRange::ExtendBackward
) {
4525 ExtendRangeToSelectCharacterBackward(
4527 EditorRawDOMPointInText(caretPoint
.ContainerAs
<Text
>(),
4528 caretPoint
.Offset()));
4531 ExtendRangeToSelectCharacterForward(
4532 aRangeToDelete
, EditorRawDOMPointInText(caretPoint
.ContainerAs
<Text
>(),
4533 caretPoint
.Offset()));
4537 nsIContent
* editableContent
=
4538 howToHandleCollapsedRange
==
4539 EditorBase::HowToHandleCollapsedRange::ExtendBackward
4540 ? HTMLEditUtils::GetPreviousContent(
4541 caretPoint
, {WalkTreeOption::IgnoreNonEditableNode
},
4542 BlockInlineCheck::Unused
, &aEditingHost
)
4543 : HTMLEditUtils::GetNextContent(
4544 caretPoint
, {WalkTreeOption::IgnoreNonEditableNode
},
4545 BlockInlineCheck::Unused
, &aEditingHost
);
4546 if (!editableContent
) {
4549 while (editableContent
&& editableContent
->IsCharacterData() &&
4550 !editableContent
->Length()) {
4552 howToHandleCollapsedRange
==
4553 EditorBase::HowToHandleCollapsedRange::ExtendBackward
4554 ? HTMLEditUtils::GetPreviousContent(
4555 *editableContent
, {WalkTreeOption::IgnoreNonEditableNode
},
4556 BlockInlineCheck::Unused
, &aEditingHost
)
4557 : HTMLEditUtils::GetNextContent(
4558 *editableContent
, {WalkTreeOption::IgnoreNonEditableNode
},
4559 BlockInlineCheck::Unused
, &aEditingHost
);
4561 if (!editableContent
) {
4565 if (!editableContent
->IsText()) {
4566 IgnoredErrorResult ignoredError
;
4567 aRangeToDelete
.SelectNode(*editableContent
, ignoredError
);
4568 NS_WARNING_ASSERTION(!ignoredError
.Failed(),
4569 "nsRange::SelectNode() failed, but ignored");
4573 if (howToHandleCollapsedRange
==
4574 EditorBase::HowToHandleCollapsedRange::ExtendBackward
) {
4575 ExtendRangeToSelectCharacterBackward(
4577 EditorRawDOMPointInText::AtEndOf(*editableContent
->AsText()));
4580 ExtendRangeToSelectCharacterForward(
4581 aRangeToDelete
, EditorRawDOMPointInText(editableContent
->AsText(), 0));
4586 template <typename EditorDOMPointType
>
4587 Result
<CaretPoint
, nsresult
> HTMLEditor::DeleteTextAndTextNodesWithTransaction(
4588 const EditorDOMPointType
& aStartPoint
, const EditorDOMPointType
& aEndPoint
,
4589 TreatEmptyTextNodes aTreatEmptyTextNodes
) {
4590 if (NS_WARN_IF(!aStartPoint
.IsSet()) || NS_WARN_IF(!aEndPoint
.IsSet())) {
4591 return Err(NS_ERROR_INVALID_ARG
);
4594 // MOOSE: this routine needs to be modified to preserve the integrity of the
4597 if (aStartPoint
== aEndPoint
) {
4598 // Nothing to delete
4599 return CaretPoint(EditorDOMPoint());
4602 RefPtr
<Element
> editingHost
= ComputeEditingHost();
4603 auto DeleteEmptyContentNodeWithTransaction
=
4604 [this, &aTreatEmptyTextNodes
, &editingHost
](nsIContent
& aContent
)
4605 MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION
-> nsresult
{
4606 OwningNonNull
<nsIContent
> nodeToRemove
= aContent
;
4607 if (aTreatEmptyTextNodes
==
4608 TreatEmptyTextNodes::RemoveAllEmptyInlineAncestors
) {
4609 Element
* emptyParentElementToRemove
=
4610 HTMLEditUtils::GetMostDistantAncestorEditableEmptyInlineElement(
4611 nodeToRemove
, BlockInlineCheck::UseComputedDisplayOutsideStyle
,
4613 if (emptyParentElementToRemove
) {
4614 nodeToRemove
= *emptyParentElementToRemove
;
4617 nsresult rv
= DeleteNodeWithTransaction(nodeToRemove
);
4618 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
4619 "EditorBase::DeleteNodeWithTransaction() failed");
4623 if (aStartPoint
.GetContainer() == aEndPoint
.GetContainer() &&
4624 aStartPoint
.IsInTextNode()) {
4625 if (aTreatEmptyTextNodes
!=
4626 TreatEmptyTextNodes::KeepIfContainerOfRangeBoundaries
&&
4627 aStartPoint
.IsStartOfContainer() && aEndPoint
.IsEndOfContainer()) {
4628 nsresult rv
= DeleteEmptyContentNodeWithTransaction(
4629 MOZ_KnownLive(*aStartPoint
.template ContainerAs
<Text
>()));
4630 if (NS_FAILED(rv
)) {
4631 NS_WARNING("deleteEmptyContentNodeWithTransaction() failed");
4634 return CaretPoint(EditorDOMPoint());
4636 RefPtr
<Text
> textNode
= aStartPoint
.template ContainerAs
<Text
>();
4637 Result
<CaretPoint
, nsresult
> caretPointOrError
=
4638 DeleteTextWithTransaction(*textNode
, aStartPoint
.Offset(),
4639 aEndPoint
.Offset() - aStartPoint
.Offset());
4640 NS_WARNING_ASSERTION(caretPointOrError
.isOk(),
4641 "HTMLEditor::DeleteTextWithTransaction() failed");
4642 return caretPointOrError
;
4645 RefPtr
<nsRange
> range
=
4646 nsRange::Create(aStartPoint
.ToRawRangeBoundary(),
4647 aEndPoint
.ToRawRangeBoundary(), IgnoreErrors());
4649 NS_WARNING("nsRange::Create() failed");
4650 return Err(NS_ERROR_FAILURE
);
4653 // Collect editable text nodes in the given range.
4654 AutoTArray
<OwningNonNull
<Text
>, 16> arrayOfTextNodes
;
4656 if (NS_FAILED(iter
.Init(*range
))) {
4657 return CaretPoint(EditorDOMPoint()); // Nothing to delete in the range.
4659 iter
.AppendNodesToArray(
4660 +[](nsINode
& aNode
, void*) {
4661 MOZ_ASSERT(aNode
.IsText());
4662 return HTMLEditUtils::IsSimplyEditableNode(aNode
);
4665 EditorDOMPoint pointToPutCaret
;
4666 for (OwningNonNull
<Text
>& textNode
: arrayOfTextNodes
) {
4667 if (textNode
== aStartPoint
.GetContainer()) {
4668 if (aStartPoint
.IsEndOfContainer()) {
4671 if (aStartPoint
.IsStartOfContainer() &&
4672 aTreatEmptyTextNodes
!=
4673 TreatEmptyTextNodes::KeepIfContainerOfRangeBoundaries
) {
4674 AutoTrackDOMPoint
trackPointToPutCaret(RangeUpdaterRef(),
4676 nsresult rv
= DeleteEmptyContentNodeWithTransaction(
4677 MOZ_KnownLive(*aStartPoint
.template ContainerAs
<Text
>()));
4678 if (NS_FAILED(rv
)) {
4679 NS_WARNING("DeleteEmptyContentNodeWithTransaction() failed");
4684 AutoTrackDOMPoint
trackPointToPutCaret(RangeUpdaterRef(),
4686 Result
<CaretPoint
, nsresult
> caretPointOrError
=
4687 DeleteTextWithTransaction(MOZ_KnownLive(textNode
),
4688 aStartPoint
.Offset(),
4689 textNode
->Length() - aStartPoint
.Offset());
4690 if (MOZ_UNLIKELY(caretPointOrError
.isErr())) {
4691 NS_WARNING("HTMLEditor::DeleteTextWithTransaction() failed");
4692 return caretPointOrError
;
4694 trackPointToPutCaret
.FlushAndStopTracking();
4695 caretPointOrError
.unwrap().MoveCaretPointTo(
4696 pointToPutCaret
, {SuggestCaret::OnlyIfHasSuggestion
});
4700 if (textNode
== aEndPoint
.GetContainer()) {
4701 if (aEndPoint
.IsStartOfContainer()) {
4704 if (aEndPoint
.IsEndOfContainer() &&
4705 aTreatEmptyTextNodes
!=
4706 TreatEmptyTextNodes::KeepIfContainerOfRangeBoundaries
) {
4707 AutoTrackDOMPoint
trackPointToPutCaret(RangeUpdaterRef(),
4709 nsresult rv
= DeleteEmptyContentNodeWithTransaction(
4710 MOZ_KnownLive(*aEndPoint
.template ContainerAs
<Text
>()));
4711 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
4712 "DeleteEmptyContentNodeWithTransaction() failed");
4715 AutoTrackDOMPoint
trackPointToPutCaret(RangeUpdaterRef(),
4717 Result
<CaretPoint
, nsresult
> caretPointOrError
=
4718 DeleteTextWithTransaction(MOZ_KnownLive(textNode
), 0,
4719 aEndPoint
.Offset());
4720 if (MOZ_UNLIKELY(caretPointOrError
.isErr())) {
4721 NS_WARNING("HTMLEditor::DeleteTextWithTransaction() failed");
4722 return caretPointOrError
;
4724 trackPointToPutCaret
.FlushAndStopTracking();
4725 caretPointOrError
.unwrap().MoveCaretPointTo(
4726 pointToPutCaret
, {SuggestCaret::OnlyIfHasSuggestion
});
4727 return CaretPoint(pointToPutCaret
);
4731 DeleteEmptyContentNodeWithTransaction(MOZ_KnownLive(textNode
));
4732 if (NS_FAILED(rv
)) {
4733 NS_WARNING("DeleteEmptyContentNodeWithTransaction() failed");
4738 return CaretPoint(pointToPutCaret
);
4741 Result
<EditorDOMPoint
, nsresult
> HTMLEditor::AutoDeleteRangesHandler::
4742 AutoBlockElementsJoiner::JoinNodesDeepWithTransaction(
4743 HTMLEditor
& aHTMLEditor
, nsIContent
& aLeftContent
,
4744 nsIContent
& aRightContent
) {
4745 // While the rightmost children and their descendants of the left node match
4746 // the leftmost children and their descendants of the right node, join them
4749 nsCOMPtr
<nsIContent
> leftContentToJoin
= &aLeftContent
;
4750 nsCOMPtr
<nsIContent
> rightContentToJoin
= &aRightContent
;
4751 nsCOMPtr
<nsINode
> parentNode
= aRightContent
.GetParentNode();
4754 while (leftContentToJoin
&& rightContentToJoin
&& parentNode
&&
4755 HTMLEditUtils::CanContentsBeJoined(*leftContentToJoin
,
4756 *rightContentToJoin
)) {
4758 Result
<JoinNodesResult
, nsresult
> joinNodesResult
=
4759 aHTMLEditor
.JoinNodesWithTransaction(*leftContentToJoin
,
4760 *rightContentToJoin
);
4761 if (MOZ_UNLIKELY(joinNodesResult
.isErr())) {
4762 NS_WARNING("HTMLEditor::JoinNodesWithTransaction() failed");
4763 return joinNodesResult
.propagateErr();
4766 ret
= joinNodesResult
.inspect().AtJoinedPoint
<EditorDOMPoint
>();
4767 if (NS_WARN_IF(!ret
.IsSet())) {
4768 return Err(NS_ERROR_FAILURE
);
4771 if (parentNode
->IsText()) {
4772 // We've joined all the way down to text nodes, we're done!
4776 // Get new left and right nodes, and begin anew
4777 rightContentToJoin
= ret
.GetCurrentChildAtOffset();
4778 if (rightContentToJoin
) {
4779 leftContentToJoin
= rightContentToJoin
->GetPreviousSibling();
4781 leftContentToJoin
= nullptr;
4784 // Skip over non-editable nodes
4785 while (leftContentToJoin
&& !EditorUtils::IsEditableContent(
4786 *leftContentToJoin
, EditorType::HTML
)) {
4787 leftContentToJoin
= leftContentToJoin
->GetPreviousSibling();
4789 if (!leftContentToJoin
) {
4793 while (rightContentToJoin
&& !EditorUtils::IsEditableContent(
4794 *rightContentToJoin
, EditorType::HTML
)) {
4795 rightContentToJoin
= rightContentToJoin
->GetNextSibling();
4797 if (!rightContentToJoin
) {
4803 NS_WARNING("HTMLEditor::JoinNodesDeepWithTransaction() joined no contents");
4804 return Err(NS_ERROR_FAILURE
);
4809 Result
<bool, nsresult
> HTMLEditor::AutoDeleteRangesHandler::
4810 AutoBlockElementsJoiner::AutoInclusiveAncestorBlockElementsJoiner::Prepare(
4811 const HTMLEditor
& aHTMLEditor
, const Element
& aEditingHost
) {
4812 mLeftBlockElement
= HTMLEditUtils::GetInclusiveAncestorElement(
4813 mInclusiveDescendantOfLeftBlockElement
,
4814 HTMLEditUtils::ClosestEditableBlockElementExceptHRElement
,
4815 BlockInlineCheck::UseComputedDisplayOutsideStyle
);
4816 mRightBlockElement
= HTMLEditUtils::GetInclusiveAncestorElement(
4817 mInclusiveDescendantOfRightBlockElement
,
4818 HTMLEditUtils::ClosestEditableBlockElementExceptHRElement
,
4819 BlockInlineCheck::UseComputedDisplayOutsideStyle
);
4821 if (NS_WARN_IF(!IsSet())) {
4822 mCanJoinBlocks
= false;
4823 return Err(NS_ERROR_UNEXPECTED
);
4826 // Don't join the blocks if both of them are basic structure of the HTML
4827 // document (Note that `<body>` can be joined with its children).
4828 if (mLeftBlockElement
->IsAnyOfHTMLElements(nsGkAtoms::html
, nsGkAtoms::head
,
4830 mRightBlockElement
->IsAnyOfHTMLElements(nsGkAtoms::html
, nsGkAtoms::head
,
4832 mCanJoinBlocks
= false;
4836 if (HTMLEditUtils::IsAnyTableElement(mLeftBlockElement
) ||
4837 HTMLEditUtils::IsAnyTableElement(mRightBlockElement
)) {
4838 // Do not try to merge table elements, cancel the deletion.
4839 mCanJoinBlocks
= false;
4843 // Bail if both blocks the same
4844 if (IsSameBlockElement()) {
4845 mCanJoinBlocks
= true; // XXX Anyway, Run() will ingore this case.
4846 mFallbackToDeleteLeafContent
= true;
4850 // Joining a list item to its parent is a NOP.
4851 if (HTMLEditUtils::IsAnyListElement(mLeftBlockElement
) &&
4852 HTMLEditUtils::IsListItem(mRightBlockElement
) &&
4853 mRightBlockElement
->GetParentNode() == mLeftBlockElement
) {
4854 mCanJoinBlocks
= false;
4858 // Special rule here: if we are trying to join list items, and they are in
4859 // different lists, join the lists instead.
4860 if (HTMLEditUtils::IsListItem(mLeftBlockElement
) &&
4861 HTMLEditUtils::IsListItem(mRightBlockElement
)) {
4862 // XXX leftListElement and/or rightListElement may be not list elements.
4863 Element
* leftListElement
= mLeftBlockElement
->GetParentElement();
4864 Element
* rightListElement
= mRightBlockElement
->GetParentElement();
4865 EditorDOMPoint atChildInBlock
;
4866 if (leftListElement
&& rightListElement
&&
4867 leftListElement
!= rightListElement
&&
4868 !EditorUtils::IsDescendantOf(*leftListElement
, *mRightBlockElement
,
4870 !EditorUtils::IsDescendantOf(*rightListElement
, *mLeftBlockElement
,
4872 // There are some special complications if the lists are descendants of
4873 // the other lists' items. Note that it is okay for them to be
4874 // descendants of the other lists themselves, which is the usual case for
4875 // sublists in our implementation.
4876 MOZ_DIAGNOSTIC_ASSERT(!atChildInBlock
.IsSet());
4877 mLeftBlockElement
= leftListElement
;
4878 mRightBlockElement
= rightListElement
;
4879 mNewListElementTagNameOfRightListElement
=
4880 Some(leftListElement
->NodeInfo()->NameAtom());
4884 if (!EditorUtils::IsDescendantOf(*mLeftBlockElement
, *mRightBlockElement
,
4885 &mPointContainingTheOtherBlockElement
)) {
4886 Unused
<< EditorUtils::IsDescendantOf(
4887 *mRightBlockElement
, *mLeftBlockElement
,
4888 &mPointContainingTheOtherBlockElement
);
4891 if (mPointContainingTheOtherBlockElement
.GetContainer() ==
4892 mRightBlockElement
) {
4893 mPrecedingInvisibleBRElement
=
4894 WSRunScanner::GetPrecedingBRElementUnlessVisibleContentFound(
4895 aHTMLEditor
.ComputeEditingHost(),
4896 EditorDOMPoint::AtEndOf(mLeftBlockElement
),
4897 BlockInlineCheck::UseComputedDisplayOutsideStyle
);
4898 // `WhiteSpaceVisibilityKeeper::
4899 // MergeFirstLineOfRightBlockElementIntoDescendantLeftBlockElement()`
4900 // returns ignored when:
4901 // - No preceding invisible `<br>` element and
4902 // - mNewListElementTagNameOfRightListElement is nothing and
4903 // - There is no content to move from right block element.
4904 if (!mPrecedingInvisibleBRElement
) {
4905 if (CanMergeLeftAndRightBlockElements()) {
4906 // Always marked as handled in this case.
4907 mFallbackToDeleteLeafContent
= false;
4909 // Marked as handled only when it actually moves a content node.
4910 Result
<bool, nsresult
> firstLineHasContent
=
4911 AutoMoveOneLineHandler::CanMoveOrDeleteSomethingInLine(
4912 mPointContainingTheOtherBlockElement
4913 .NextPoint
<EditorDOMPoint
>(),
4915 mFallbackToDeleteLeafContent
=
4916 firstLineHasContent
.isOk() && !firstLineHasContent
.inspect();
4919 // Marked as handled when deleting the invisible `<br>` element.
4920 mFallbackToDeleteLeafContent
= false;
4922 } else if (mPointContainingTheOtherBlockElement
.GetContainer() ==
4923 mLeftBlockElement
) {
4924 mPrecedingInvisibleBRElement
=
4925 WSRunScanner::GetPrecedingBRElementUnlessVisibleContentFound(
4926 aHTMLEditor
.ComputeEditingHost(),
4927 mPointContainingTheOtherBlockElement
,
4928 BlockInlineCheck::UseComputedDisplayOutsideStyle
);
4929 // `WhiteSpaceVisibilityKeeper::
4930 // MergeFirstLineOfRightBlockElementIntoAncestorLeftBlockElement()`
4931 // returns ignored when:
4932 // - No preceding invisible `<br>` element and
4933 // - mNewListElementTagNameOfRightListElement is some and
4934 // - The right block element has no children
4936 // - No preceding invisible `<br>` element and
4937 // - mNewListElementTagNameOfRightListElement is nothing and
4938 // - There is no content to move from right block element.
4939 if (!mPrecedingInvisibleBRElement
) {
4940 if (CanMergeLeftAndRightBlockElements()) {
4941 // Marked as handled only when it actualy moves a content node.
4942 Result
<bool, nsresult
> rightBlockHasContent
=
4943 aHTMLEditor
.CanMoveChildren(*mRightBlockElement
,
4944 *mLeftBlockElement
);
4945 mFallbackToDeleteLeafContent
=
4946 rightBlockHasContent
.isOk() && !rightBlockHasContent
.inspect();
4948 // Marked as handled only when it actually moves a content node.
4949 Result
<bool, nsresult
> firstLineHasContent
=
4950 AutoMoveOneLineHandler::CanMoveOrDeleteSomethingInLine(
4951 EditorDOMPoint(mRightBlockElement
, 0u), aEditingHost
);
4952 mFallbackToDeleteLeafContent
=
4953 firstLineHasContent
.isOk() && !firstLineHasContent
.inspect();
4956 // Marked as handled when deleting the invisible `<br>` element.
4957 mFallbackToDeleteLeafContent
= false;
4960 mPrecedingInvisibleBRElement
=
4961 WSRunScanner::GetPrecedingBRElementUnlessVisibleContentFound(
4962 aHTMLEditor
.ComputeEditingHost(),
4963 EditorDOMPoint::AtEndOf(mLeftBlockElement
),
4964 BlockInlineCheck::UseComputedDisplayOutsideStyle
);
4965 // `WhiteSpaceVisibilityKeeper::
4966 // MergeFirstLineOfRightBlockElementIntoLeftBlockElement()` always
4967 // return "handled".
4968 mFallbackToDeleteLeafContent
= false;
4971 mCanJoinBlocks
= true;
4975 nsresult
HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
4976 AutoInclusiveAncestorBlockElementsJoiner::ComputeRangeToDelete(
4977 const HTMLEditor
& aHTMLEditor
, const EditorDOMPoint
& aCaretPoint
,
4978 nsRange
& aRangeToDelete
) const {
4979 MOZ_ASSERT(mLeftBlockElement
);
4980 MOZ_ASSERT(mRightBlockElement
);
4982 if (IsSameBlockElement()) {
4983 if (!aCaretPoint
.IsSet()) {
4984 return NS_OK
; // The ranges are not collapsed, keep them as-is.
4986 nsresult rv
= aRangeToDelete
.CollapseTo(aCaretPoint
.ToRawRangeBoundary());
4987 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
), "nsRange::CollapseTo() failed");
4991 EditorDOMPoint pointContainingTheOtherBlock
;
4992 if (!EditorUtils::IsDescendantOf(*mLeftBlockElement
, *mRightBlockElement
,
4993 &pointContainingTheOtherBlock
)) {
4994 Unused
<< EditorUtils::IsDescendantOf(
4995 *mRightBlockElement
, *mLeftBlockElement
, &pointContainingTheOtherBlock
);
4997 EditorDOMRange range
=
4998 WSRunScanner::GetRangeForDeletingBlockElementBoundaries(
4999 aHTMLEditor
, *mLeftBlockElement
, *mRightBlockElement
,
5000 pointContainingTheOtherBlock
);
5001 if (!range
.IsPositioned()) {
5003 "WSRunScanner::GetRangeForDeletingBlockElementBoundaries() failed");
5004 return NS_ERROR_FAILURE
;
5006 if (!aCaretPoint
.IsSet()) {
5007 // Don't shrink the original range.
5008 bool noNeedToChangeStart
= false;
5009 const EditorDOMPoint
atStart(aRangeToDelete
.StartRef());
5010 if (atStart
.IsBefore(range
.StartRef())) {
5011 // If the range starts from end of a container, and computed block
5012 // boundaries range starts from an invisible `<br>` element, we
5013 // may need to shrink the range.
5014 Element
* editingHost
= aHTMLEditor
.ComputeEditingHost();
5015 NS_WARNING_ASSERTION(editingHost
, "There was no editing host");
5016 nsIContent
* nextContent
=
5017 atStart
.IsEndOfContainer() && range
.StartRef().GetChild() &&
5018 HTMLEditUtils::IsInvisibleBRElement(
5019 *range
.StartRef().GetChild())
5020 ? HTMLEditUtils::GetNextContent(
5021 *atStart
.ContainerAs
<nsIContent
>(),
5022 {WalkTreeOption::IgnoreDataNodeExceptText
,
5023 WalkTreeOption::StopAtBlockBoundary
},
5024 BlockInlineCheck::UseComputedDisplayOutsideStyle
,
5027 if (!nextContent
|| nextContent
!= range
.StartRef().GetChild()) {
5028 noNeedToChangeStart
= true;
5029 range
.SetStart(EditorRawDOMPoint(aRangeToDelete
.StartRef()));
5032 if (range
.EndRef().IsBefore(EditorRawDOMPoint(aRangeToDelete
.EndRef()))) {
5033 if (noNeedToChangeStart
) {
5034 return NS_OK
; // We don't need to modify the range.
5036 range
.SetEnd(EditorRawDOMPoint(aRangeToDelete
.EndRef()));
5040 aRangeToDelete
.SetStartAndEnd(range
.StartRef().ToRawRangeBoundary(),
5041 range
.EndRef().ToRawRangeBoundary());
5042 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
5043 "AutoRangeArray::SetStartAndEnd() failed");
5047 Result
<EditActionResult
, nsresult
> HTMLEditor::AutoDeleteRangesHandler::
5048 AutoBlockElementsJoiner::AutoInclusiveAncestorBlockElementsJoiner::Run(
5049 HTMLEditor
& aHTMLEditor
, const Element
& aEditingHost
) {
5050 MOZ_ASSERT(aHTMLEditor
.IsEditActionDataAvailable());
5051 MOZ_ASSERT(mLeftBlockElement
);
5052 MOZ_ASSERT(mRightBlockElement
);
5054 if (IsSameBlockElement()) {
5055 return EditActionResult::IgnoredResult();
5058 if (!mCanJoinBlocks
) {
5059 return EditActionResult::HandledResult();
5062 EditorDOMPoint startOfRightContent
;
5064 // If the left block element is in the right block element, move the hard
5065 // line including the right block element to end of the left block.
5066 // However, if we are merging list elements, we don't join them.
5067 Result
<EditActionResult
, nsresult
> result(NS_ERROR_NOT_INITIALIZED
);
5068 if (mPointContainingTheOtherBlockElement
.GetContainer() ==
5069 mRightBlockElement
) {
5070 startOfRightContent
= mPointContainingTheOtherBlockElement
.NextPoint();
5071 if (Element
* element
= startOfRightContent
.GetChildAs
<Element
>()) {
5072 startOfRightContent
=
5073 HTMLEditUtils::GetDeepestEditableStartPointOf
<EditorDOMPoint
>(
5076 AutoTrackDOMPoint
trackStartOfRightBlock(aHTMLEditor
.RangeUpdaterRef(),
5077 &startOfRightContent
);
5078 result
= WhiteSpaceVisibilityKeeper::
5079 MergeFirstLineOfRightBlockElementIntoDescendantLeftBlockElement(
5080 aHTMLEditor
, MOZ_KnownLive(*mLeftBlockElement
),
5081 MOZ_KnownLive(*mRightBlockElement
),
5082 mPointContainingTheOtherBlockElement
,
5083 mNewListElementTagNameOfRightListElement
,
5084 MOZ_KnownLive(mPrecedingInvisibleBRElement
), aEditingHost
);
5085 if (MOZ_UNLIKELY(result
.isErr())) {
5087 "WhiteSpaceVisibilityKeeper::"
5088 "MergeFirstLineOfRightBlockElementIntoDescendantLeftBlockElement() "
5092 if (NS_WARN_IF(!startOfRightContent
.IsSet()) ||
5093 NS_WARN_IF(!startOfRightContent
.GetContainer()->IsInComposedDoc())) {
5094 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
);
5098 // If the right block element is in the left block element:
5099 // - move list item elements in the right block element to where the left
5101 // - or first hard line in the right block element to where:
5102 // - the left block element is.
5103 // - or the given left content in the left block is.
5104 else if (mPointContainingTheOtherBlockElement
.GetContainer() ==
5105 mLeftBlockElement
) {
5106 startOfRightContent
=
5107 HTMLEditUtils::GetDeepestEditableStartPointOf
<EditorDOMPoint
>(
5108 *mRightBlockElement
);
5109 AutoTrackDOMPoint
trackStartOfRightBlock(aHTMLEditor
.RangeUpdaterRef(),
5110 &startOfRightContent
);
5111 result
= WhiteSpaceVisibilityKeeper::
5112 MergeFirstLineOfRightBlockElementIntoAncestorLeftBlockElement(
5113 aHTMLEditor
, MOZ_KnownLive(*mLeftBlockElement
),
5114 MOZ_KnownLive(*mRightBlockElement
),
5115 mPointContainingTheOtherBlockElement
,
5116 MOZ_KnownLive(*mInclusiveDescendantOfLeftBlockElement
),
5117 mNewListElementTagNameOfRightListElement
,
5118 MOZ_KnownLive(mPrecedingInvisibleBRElement
), aEditingHost
);
5119 if (MOZ_UNLIKELY(result
.isErr())) {
5121 "WhiteSpaceVisibilityKeeper::"
5122 "MergeFirstLineOfRightBlockElementIntoAncestorLeftBlockElement() "
5126 trackStartOfRightBlock
.FlushAndStopTracking();
5127 if (NS_WARN_IF(!startOfRightContent
.IsSet()) ||
5128 NS_WARN_IF(!startOfRightContent
.GetContainer()->IsInComposedDoc())) {
5129 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
);
5134 // Normal case. Blocks are siblings, or at least close enough. An example
5135 // of the latter is <p>paragraph</p><ul><li>one<li>two<li>three</ul>. The
5136 // first li and the p are not true siblings, but we still want to join them
5137 // if you backspace from li into p.
5139 MOZ_ASSERT(!mPointContainingTheOtherBlockElement
.IsSet());
5141 startOfRightContent
=
5142 HTMLEditUtils::GetDeepestEditableStartPointOf
<EditorDOMPoint
>(
5143 *mRightBlockElement
);
5144 AutoTrackDOMPoint
trackStartOfRightBlock(aHTMLEditor
.RangeUpdaterRef(),
5145 &startOfRightContent
);
5146 result
= WhiteSpaceVisibilityKeeper::
5147 MergeFirstLineOfRightBlockElementIntoLeftBlockElement(
5148 aHTMLEditor
, MOZ_KnownLive(*mLeftBlockElement
),
5149 MOZ_KnownLive(*mRightBlockElement
),
5150 mNewListElementTagNameOfRightListElement
,
5151 MOZ_KnownLive(mPrecedingInvisibleBRElement
), aEditingHost
);
5152 if (MOZ_UNLIKELY(result
.isErr())) {
5154 "WhiteSpaceVisibilityKeeper::"
5155 "MergeFirstLineOfRightBlockElementIntoLeftBlockElement() failed");
5158 trackStartOfRightBlock
.FlushAndStopTracking();
5159 if (NS_WARN_IF(!startOfRightContent
.IsSet()) ||
5160 NS_WARN_IF(!startOfRightContent
.GetContainer()->IsInComposedDoc())) {
5161 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
);
5165 // If we're deleting selection (meaning not replacing selection with new
5166 // content), we should put caret to end of preceding text node if there is.
5167 // Then, users can type text into it like the other browsers.
5168 if (MayEditActionDeleteAroundCollapsedSelection(
5169 aHTMLEditor
.GetEditAction())) {
5170 WSRunScanner
scanner(&aEditingHost
, startOfRightContent
,
5171 BlockInlineCheck::UseComputedDisplayStyle
);
5172 WSScanResult maybePreviousText
=
5173 scanner
.ScanPreviousVisibleNodeOrBlockBoundaryFrom(startOfRightContent
);
5174 if (maybePreviousText
.IsContentEditable() &&
5175 maybePreviousText
.InVisibleOrCollapsibleCharacters()) {
5176 mPointToPutCaret
= maybePreviousText
.Point
<EditorDOMPoint
>();
5183 Result
<bool, nsresult
>
5184 HTMLEditor::AutoMoveOneLineHandler::CanMoveOrDeleteSomethingInLine(
5185 const EditorDOMPoint
& aPointInHardLine
, const Element
& aEditingHost
) {
5186 if (NS_WARN_IF(!aPointInHardLine
.IsSet()) ||
5187 NS_WARN_IF(aPointInHardLine
.IsInNativeAnonymousSubtree())) {
5188 return Err(NS_ERROR_INVALID_ARG
);
5191 RefPtr
<nsRange
> oneLineRange
=
5192 AutoRangeArray::CreateRangeWrappingStartAndEndLinesContainingBoundaries(
5193 aPointInHardLine
, aPointInHardLine
,
5194 EditSubAction::eMergeBlockContents
,
5195 BlockInlineCheck::UseComputedDisplayOutsideStyle
, aEditingHost
);
5196 if (!oneLineRange
|| oneLineRange
->Collapsed() ||
5197 !oneLineRange
->IsPositioned() ||
5198 !oneLineRange
->GetStartContainer()->IsContent() ||
5199 !oneLineRange
->GetEndContainer()->IsContent()) {
5203 // If there is only a padding `<br>` element in a empty block, it's selected
5204 // by `UpdatePointsToSelectAllChildrenIfCollapsedInEmptyBlockElement()`.
5205 // However, it won't be moved. Although it'll be deleted,
5206 // AutoMoveOneLineHandler returns "ignored". Therefore, we should return
5207 // `false` in this case.
5208 if (nsIContent
* childContent
= oneLineRange
->GetChildAtStartOffset()) {
5209 if (childContent
->IsHTMLElement(nsGkAtoms::br
) &&
5210 childContent
->GetParent()) {
5211 if (const Element
* blockElement
=
5212 HTMLEditUtils::GetInclusiveAncestorElement(
5213 *childContent
->GetParent(),
5214 HTMLEditUtils::ClosestBlockElement
,
5215 BlockInlineCheck::UseComputedDisplayOutsideStyle
)) {
5216 if (HTMLEditUtils::IsEmptyNode(
5218 {EmptyCheckOption::TreatNonEditableContentAsInvisible
})) {
5225 nsINode
* commonAncestor
= oneLineRange
->GetClosestCommonInclusiveAncestor();
5226 // Currently, we move non-editable content nodes too.
5227 EditorRawDOMPoint
startPoint(oneLineRange
->StartRef());
5228 if (!startPoint
.IsEndOfContainer()) {
5231 EditorRawDOMPoint
endPoint(oneLineRange
->EndRef());
5232 if (!endPoint
.IsStartOfContainer()) {
5235 if (startPoint
.GetContainer() != commonAncestor
) {
5237 EditorRawDOMPoint
pointInParent(startPoint
.GetContainerAs
<nsIContent
>());
5238 if (NS_WARN_IF(!pointInParent
.IsInContentNode())) {
5239 return Err(NS_ERROR_FAILURE
);
5241 if (pointInParent
.GetContainer() == commonAncestor
) {
5242 startPoint
= pointInParent
;
5245 if (!pointInParent
.IsEndOfContainer()) {
5250 if (endPoint
.GetContainer() != commonAncestor
) {
5252 EditorRawDOMPoint
pointInParent(endPoint
.GetContainerAs
<nsIContent
>());
5253 if (NS_WARN_IF(!pointInParent
.IsInContentNode())) {
5254 return Err(NS_ERROR_FAILURE
);
5256 if (pointInParent
.GetContainer() == commonAncestor
) {
5257 endPoint
= pointInParent
;
5260 if (!pointInParent
.IsStartOfContainer()) {
5265 // If start point and end point in the common ancestor are direct siblings,
5266 // there is no content to move or delete.
5267 // E.g., `<b>abc<br>[</b><i>]<br>def</i>`.
5268 return startPoint
.GetNextSiblingOfChild() != endPoint
.GetChild();
5271 nsresult
HTMLEditor::AutoMoveOneLineHandler::Prepare(
5272 HTMLEditor
& aHTMLEditor
, const EditorDOMPoint
& aPointInHardLine
,
5273 const Element
& aEditingHost
) {
5274 MOZ_ASSERT(aHTMLEditor
.IsEditActionDataAvailable());
5275 MOZ_ASSERT(aPointInHardLine
.IsInContentNode());
5276 MOZ_ASSERT(mPointToInsert
.IsSetAndValid());
5278 MOZ_LOG(gOneLineMoverLog
, LogLevel::Info
,
5279 ("Prepare(aHTMLEditor=%p, aPointInHardLine=%s, aEditingHost=%s), "
5280 "mPointToInsert=%s, mMoveToEndOfContainer=%s",
5281 &aHTMLEditor
, ToString(aPointInHardLine
).c_str(),
5282 ToString(aEditingHost
).c_str(), ToString(mPointToInsert
).c_str(),
5283 ForceMoveToEndOfContainer() ? "MoveToEndOfContainer::Yes"
5284 : "MoveToEndOfContainer::No"));
5286 if (NS_WARN_IF(mPointToInsert
.IsInNativeAnonymousSubtree())) {
5288 gOneLineMoverLog
, LogLevel::Error
,
5289 ("Failed because mPointToInsert was in a native anonymous subtree"));
5290 return Err(NS_ERROR_INVALID_ARG
);
5293 mSrcInclusiveAncestorBlock
=
5294 aPointInHardLine
.IsInContentNode()
5295 ? HTMLEditUtils::GetInclusiveAncestorElement(
5296 *aPointInHardLine
.ContainerAs
<nsIContent
>(),
5297 HTMLEditUtils::ClosestBlockElement
,
5298 BlockInlineCheck::UseComputedDisplayOutsideStyle
)
5300 mDestInclusiveAncestorBlock
=
5301 mPointToInsert
.IsInContentNode()
5302 ? HTMLEditUtils::GetInclusiveAncestorElement(
5303 *mPointToInsert
.ContainerAs
<nsIContent
>(),
5304 HTMLEditUtils::ClosestBlockElement
,
5305 BlockInlineCheck::UseComputedDisplayOutsideStyle
)
5307 mMovingToParentBlock
=
5308 mDestInclusiveAncestorBlock
&& mSrcInclusiveAncestorBlock
&&
5309 mDestInclusiveAncestorBlock
!= mSrcInclusiveAncestorBlock
&&
5310 mSrcInclusiveAncestorBlock
->IsInclusiveDescendantOf(
5311 mDestInclusiveAncestorBlock
);
5312 mTopmostSrcAncestorBlockInDestBlock
=
5313 mMovingToParentBlock
5314 ? AutoMoveOneLineHandler::
5315 GetMostDistantInclusiveAncestorBlockInSpecificAncestorElement(
5316 *mSrcInclusiveAncestorBlock
, *mDestInclusiveAncestorBlock
)
5318 MOZ_ASSERT_IF(mMovingToParentBlock
, mTopmostSrcAncestorBlockInDestBlock
);
5320 mPreserveWhiteSpaceStyle
=
5321 AutoMoveOneLineHandler::ConsiderWhetherPreserveWhiteSpaceStyle(
5322 aPointInHardLine
.GetContainerAs
<nsIContent
>(),
5323 mDestInclusiveAncestorBlock
);
5325 AutoRangeArray
rangesToWrapTheLine(aPointInHardLine
);
5326 rangesToWrapTheLine
.ExtendRangesToWrapLines(
5327 EditSubAction::eMergeBlockContents
,
5328 BlockInlineCheck::UseComputedDisplayOutsideStyle
, aEditingHost
);
5329 MOZ_ASSERT(rangesToWrapTheLine
.Ranges().Length() <= 1u);
5330 mLineRange
= EditorDOMRange(rangesToWrapTheLine
.FirstRangeRef());
5332 MOZ_LOG(gOneLineMoverLog
, LogLevel::Info
,
5333 ("mSrcInclusiveAncestorBlock=%s, mDestInclusiveAncestorBlock=%s, "
5334 "mMovingToParentBlock=%s, mTopmostSrcAncestorBlockInDestBlock=%s, "
5335 "mPreserveWhiteSpaceStyle=%s, mLineRange=%s",
5336 mSrcInclusiveAncestorBlock
5337 ? ToString(*mSrcInclusiveAncestorBlock
).c_str()
5339 mDestInclusiveAncestorBlock
5340 ? ToString(*mDestInclusiveAncestorBlock
).c_str()
5342 mMovingToParentBlock
? "true" : "false",
5343 mTopmostSrcAncestorBlockInDestBlock
5344 ? ToString(*mTopmostSrcAncestorBlockInDestBlock
).c_str()
5346 ToString(mPreserveWhiteSpaceStyle
).c_str(),
5347 ToString(mLineRange
).c_str()));
5352 Result
<CaretPoint
, nsresult
>
5353 HTMLEditor::AutoMoveOneLineHandler::SplitToMakeTheLineIsolated(
5354 HTMLEditor
& aHTMLEditor
, const nsIContent
& aNewContainer
,
5355 const Element
& aEditingHost
,
5356 nsTArray
<OwningNonNull
<nsIContent
>>& aOutArrayOfContents
) const {
5357 AutoRangeArray
rangesToWrapTheLine(mLineRange
);
5358 Result
<EditorDOMPoint
, nsresult
> splitResult
=
5360 .SplitTextAtEndBoundariesAndInlineAncestorsAtBothBoundaries(
5361 aHTMLEditor
, BlockInlineCheck::UseComputedDisplayOutsideStyle
,
5362 aEditingHost
, &aNewContainer
);
5363 if (MOZ_UNLIKELY(splitResult
.isErr())) {
5366 "SplitTextAtEndBoundariesAndInlineAncestorsAtBothBoundaries() failed");
5367 return Err(splitResult
.unwrapErr());
5369 EditorDOMPoint pointToPutCaret
;
5370 if (splitResult
.inspect().IsSet()) {
5371 pointToPutCaret
= splitResult
.unwrap();
5373 nsresult rv
= rangesToWrapTheLine
.CollectEditTargetNodes(
5374 aHTMLEditor
, aOutArrayOfContents
, EditSubAction::eMergeBlockContents
,
5375 AutoRangeArray::CollectNonEditableNodes::Yes
);
5376 if (NS_FAILED(rv
)) {
5378 "AutoRangeArray::CollectEditTargetNodes(EditSubAction::"
5379 "eMergeBlockContents, CollectNonEditableNodes::Yes) failed");
5382 return CaretPoint(pointToPutCaret
);
5386 Element
* HTMLEditor::AutoMoveOneLineHandler::
5387 GetMostDistantInclusiveAncestorBlockInSpecificAncestorElement(
5388 Element
& aBlockElement
, const Element
& aAncestorElement
) {
5389 MOZ_ASSERT(aBlockElement
.IsInclusiveDescendantOf(&aAncestorElement
));
5390 MOZ_ASSERT(HTMLEditUtils::IsBlockElement(
5391 aBlockElement
, BlockInlineCheck::UseComputedDisplayOutsideStyle
));
5393 if (&aBlockElement
== &aAncestorElement
) {
5397 Element
* lastBlockAncestor
= &aBlockElement
;
5398 for (Element
* element
: aBlockElement
.InclusiveAncestorsOfType
<Element
>()) {
5399 if (element
== &aAncestorElement
) {
5400 return lastBlockAncestor
;
5402 if (HTMLEditUtils::IsBlockElement(
5404 BlockInlineCheck::UseComputedDisplayOutsideStyle
)) {
5405 lastBlockAncestor
= element
;
5412 HTMLEditor::PreserveWhiteSpaceStyle
5413 HTMLEditor::AutoMoveOneLineHandler::ConsiderWhetherPreserveWhiteSpaceStyle(
5414 const nsIContent
* aContentInLine
,
5415 const Element
* aInclusiveAncestorBlockOfInsertionPoint
) {
5416 if (MOZ_UNLIKELY(!aInclusiveAncestorBlockOfInsertionPoint
)) {
5417 return PreserveWhiteSpaceStyle::No
;
5420 // If we move content from or to <pre>, we don't need to preserve the
5421 // white-space style for compatibility with both our traditional behavior
5422 // and the other browsers.
5424 // TODO: If `white-space` is specified by non-UA stylesheet, we should
5425 // preserve it even if the right block is <pre> for compatibility with the
5427 const auto IsInclusiveDescendantOfPre
= [](const nsIContent
& aContent
) {
5428 // If the content has different `white-space` style from <pre>, we
5429 // shouldn't treat it as a descendant of <pre> because web apps or
5430 // the user intent to treat the white-spaces in aContent not as `pre`.
5431 if (EditorUtils::GetComputedWhiteSpaceStyles(aContent
).valueOr(std::pair(
5432 StyleWhiteSpaceCollapse::Collapse
, StyleTextWrapMode::Wrap
)) !=
5433 std::pair(StyleWhiteSpaceCollapse::Preserve
,
5434 StyleTextWrapMode::Nowrap
)) {
5437 for (const Element
* element
:
5438 aContent
.InclusiveAncestorsOfType
<Element
>()) {
5439 if (element
->IsHTMLElement(nsGkAtoms::pre
)) {
5445 if (IsInclusiveDescendantOfPre(*aInclusiveAncestorBlockOfInsertionPoint
) ||
5446 MOZ_UNLIKELY(!aContentInLine
) ||
5447 IsInclusiveDescendantOfPre(*aContentInLine
)) {
5448 return PreserveWhiteSpaceStyle::No
;
5450 return PreserveWhiteSpaceStyle::Yes
;
5453 Result
<MoveNodeResult
, nsresult
> HTMLEditor::AutoMoveOneLineHandler::Run(
5454 HTMLEditor
& aHTMLEditor
, const Element
& aEditingHost
) {
5455 EditorDOMPoint
pointToInsert(NextInsertionPointRef());
5456 MOZ_ASSERT(pointToInsert
.IsInContentNode());
5459 gOneLineMoverLog
, LogLevel::Info
,
5460 ("Run(aHTMLEditor=%p, aEditingHost=%s), pointToInsert=%s", &aHTMLEditor
,
5461 ToString(aEditingHost
).c_str(), ToString(pointToInsert
).c_str()));
5463 EditorDOMPoint pointToPutCaret
;
5464 AutoTArray
<OwningNonNull
<nsIContent
>, 64> arrayOfContents
;
5466 AutoTrackDOMPoint
tackPointToInsert(aHTMLEditor
.RangeUpdaterRef(),
5469 Result
<CaretPoint
, nsresult
> splitAtLineEdgesResult
=
5470 SplitToMakeTheLineIsolated(
5472 MOZ_KnownLive(*pointToInsert
.ContainerAs
<nsIContent
>()),
5473 aEditingHost
, arrayOfContents
);
5474 if (MOZ_UNLIKELY(splitAtLineEdgesResult
.isErr())) {
5475 NS_WARNING("AutoMoveOneLineHandler::SplitToMakeTheLineIsolated() failed");
5476 MOZ_LOG(gOneLineMoverLog
, LogLevel::Error
,
5477 ("Run: SplitToMakeTheLineIsolated() failed"));
5478 return splitAtLineEdgesResult
.propagateErr();
5480 splitAtLineEdgesResult
.unwrap().MoveCaretPointTo(
5481 pointToPutCaret
, {SuggestCaret::OnlyIfHasSuggestion
});
5482 MOZ_LOG(gOneLineMoverLog
, LogLevel::Verbose
,
5483 ("Run: pointToPutCaret=%s", ToString(pointToPutCaret
).c_str()));
5485 Result
<EditorDOMPoint
, nsresult
> splitAtBRElementsResult
=
5486 aHTMLEditor
.MaybeSplitElementsAtEveryBRElement(
5487 arrayOfContents
, EditSubAction::eMergeBlockContents
);
5488 if (MOZ_UNLIKELY(splitAtBRElementsResult
.isErr())) {
5490 "HTMLEditor::MaybeSplitElementsAtEveryBRElement(EditSubAction::"
5491 "eMergeBlockContents) failed");
5492 MOZ_LOG(gOneLineMoverLog
, LogLevel::Error
,
5493 ("Run: MaybeSplitElementsAtEveryBRElement() failed"));
5494 return splitAtBRElementsResult
.propagateErr();
5496 if (splitAtBRElementsResult
.inspect().IsSet()) {
5497 pointToPutCaret
= splitAtBRElementsResult
.unwrap();
5499 MOZ_LOG(gOneLineMoverLog
, LogLevel::Verbose
,
5500 ("Run: pointToPutCaret=%s", ToString(pointToPutCaret
).c_str()));
5503 if (!pointToInsert
.IsSetAndValid()) {
5504 MOZ_LOG(gOneLineMoverLog
, LogLevel::Error
,
5505 ("Run: Failed because pointToInsert pointed invalid position"));
5506 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
);
5509 if (aHTMLEditor
.AllowsTransactionsToChangeSelection() &&
5510 pointToPutCaret
.IsSet()) {
5511 nsresult rv
= aHTMLEditor
.CollapseSelectionTo(pointToPutCaret
);
5512 if (NS_FAILED(rv
)) {
5513 NS_WARNING("EditorBase::CollapseSelectionTo() failed");
5514 MOZ_LOG(gOneLineMoverLog
, LogLevel::Error
,
5515 ("Run: Failed because of "
5516 "aHTMLEditor.CollapseSelectionTo(pointToPutCaret) failure"));
5521 if (arrayOfContents
.IsEmpty()) {
5522 MOZ_LOG(gOneLineMoverLog
, LogLevel::Info
,
5523 ("Run: Did nothing because of no content to be moved"));
5524 return MoveNodeResult::IgnoredResult(std::move(pointToInsert
));
5527 // Track the range which contains the moved contents.
5528 if (ForceMoveToEndOfContainer()) {
5529 pointToInsert
= NextInsertionPointRef();
5531 EditorDOMRange
movedContentRange(pointToInsert
);
5532 MoveNodeResult moveContentsInLineResult
=
5533 MoveNodeResult::IgnoredResult(pointToInsert
);
5534 for (const OwningNonNull
<nsIContent
>& content
: arrayOfContents
) {
5535 MOZ_LOG(gOneLineMoverLog
, LogLevel::Info
,
5536 ("Run: content=%s, pointToInsert=%s, movedContentRange=%s, "
5537 "mPointToInsert=%s",
5538 ToString(content
.ref()).c_str(), ToString(pointToInsert
).c_str(),
5539 ToString(movedContentRange
).c_str(),
5540 ToString(mPointToInsert
).c_str()));
5542 AutoEditorDOMRangeChildrenInvalidator
lockOffsets(movedContentRange
);
5543 AutoTrackDOMRange
trackMovedContentRange(aHTMLEditor
.RangeUpdaterRef(),
5544 &movedContentRange
);
5545 // If the content is a block element, move all children of it to the
5546 // new container, and then, remove the (probably) empty block element.
5547 if (HTMLEditUtils::IsBlockElement(
5548 content
, BlockInlineCheck::UseComputedDisplayOutsideStyle
)) {
5549 MOZ_LOG(gOneLineMoverLog
, LogLevel::Info
,
5550 ("Run: Unwrapping children of content because of a block"));
5551 Result
<MoveNodeResult
, nsresult
> moveChildrenResult
=
5552 aHTMLEditor
.MoveChildrenWithTransaction(
5553 MOZ_KnownLive(*content
->AsElement()), pointToInsert
,
5554 mPreserveWhiteSpaceStyle
, RemoveIfCommentNode::Yes
);
5555 if (MOZ_UNLIKELY(moveChildrenResult
.isErr())) {
5556 NS_WARNING("HTMLEditor::MoveChildrenWithTransaction() failed");
5557 MOZ_LOG(gOneLineMoverLog
, LogLevel::Error
,
5558 ("Run: MoveChildrenWithTransaction() failed"));
5559 moveContentsInLineResult
.IgnoreCaretPointSuggestion();
5560 return moveChildrenResult
;
5562 moveContentsInLineResult
|= moveChildrenResult
.inspect();
5563 // MOZ_KnownLive due to bug 1620312
5565 aHTMLEditor
.DeleteNodeWithTransaction(MOZ_KnownLive(content
));
5566 if (NS_WARN_IF(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
5567 MOZ_LOG(gOneLineMoverLog
, LogLevel::Error
,
5568 ("Run: Aborted because DeleteNodeWithTransaction() caused "
5569 "destroying the editor"));
5570 moveContentsInLineResult
.IgnoreCaretPointSuggestion();
5571 return Err(NS_ERROR_EDITOR_DESTROYED
);
5573 if (NS_FAILED(rv
)) {
5575 "EditorBase::DeleteNodeWithTransaction() failed, but ignored");
5576 MOZ_LOG(gOneLineMoverLog
, LogLevel::Warning
,
5577 ("Run: Failed to delete content but the error was ignored"));
5580 // If the moving content is a comment node or an empty inline node, we
5581 // don't want it to appear in the dist paragraph.
5582 else if (content
->IsComment() ||
5583 (content
->IsText() && !content
->AsText()->TextDataLength()) ||
5584 HTMLEditUtils::IsEmptyInlineContainer(
5586 {EmptyCheckOption::TreatSingleBRElementAsVisible
,
5587 EmptyCheckOption::TreatListItemAsVisible
,
5588 EmptyCheckOption::TreatTableCellAsVisible
,
5589 EmptyCheckOption::TreatNonEditableContentAsInvisible
},
5590 BlockInlineCheck::UseComputedDisplayOutsideStyle
)) {
5591 nsCOMPtr
<nsIContent
> emptyContent
=
5592 HTMLEditUtils::GetMostDistantAncestorEditableEmptyInlineElement(
5593 content
, BlockInlineCheck::UseComputedDisplayOutsideStyle
,
5594 &aEditingHost
, pointToInsert
.ContainerAs
<nsIContent
>());
5595 if (!emptyContent
) {
5596 emptyContent
= content
;
5598 MOZ_LOG(gOneLineMoverLog
, LogLevel::Info
,
5599 ("Run: Deleting content because of %s%s",
5600 content
->IsComment() ? "a comment node"
5601 : content
->IsText() ? "an empty text node"
5602 : "an empty inline container",
5603 content
!= emptyContent
5604 ? nsPrintfCString(" (deleting topmost empty ancestor: %s)",
5605 ToString(*emptyContent
).c_str())
5608 nsresult rv
= aHTMLEditor
.DeleteNodeWithTransaction(*emptyContent
);
5609 if (NS_FAILED(rv
)) {
5610 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
5611 MOZ_LOG(gOneLineMoverLog
, LogLevel::Error
,
5612 ("Run: DeleteNodeWithTransaction() failed"));
5613 moveContentsInLineResult
.IgnoreCaretPointSuggestion();
5617 MOZ_LOG(gOneLineMoverLog
, LogLevel::Info
, ("Run: Moving content"));
5618 // MOZ_KnownLive due to bug 1620312
5619 Result
<MoveNodeResult
, nsresult
> moveNodeOrChildrenResult
=
5620 aHTMLEditor
.MoveNodeOrChildrenWithTransaction(
5621 MOZ_KnownLive(content
), pointToInsert
, mPreserveWhiteSpaceStyle
,
5622 RemoveIfCommentNode::Yes
);
5623 if (MOZ_UNLIKELY(moveNodeOrChildrenResult
.isErr())) {
5624 NS_WARNING("HTMLEditor::MoveNodeOrChildrenWithTransaction() failed");
5625 MOZ_LOG(gOneLineMoverLog
, LogLevel::Error
,
5626 ("Run: MoveNodeOrChildrenWithTransaction() failed"));
5627 moveContentsInLineResult
.IgnoreCaretPointSuggestion();
5628 return moveNodeOrChildrenResult
;
5630 moveContentsInLineResult
|= moveNodeOrChildrenResult
.inspect();
5633 MOZ_LOG(gOneLineMoverLog
, LogLevel::Info
,
5634 ("Run: movedContentRange=%s, mPointToInsert=%s",
5635 ToString(movedContentRange
).c_str(),
5636 ToString(mPointToInsert
).c_str()));
5637 moveContentsInLineResult
.MarkAsHandled();
5638 if (NS_WARN_IF(!movedContentRange
.IsPositioned())) {
5639 MOZ_LOG(gOneLineMoverLog
, LogLevel::Error
,
5640 ("Run: Failed because movedContentRange was not positioned"));
5641 moveContentsInLineResult
.IgnoreCaretPointSuggestion();
5642 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
);
5644 // For backward compatibility, we should move contents to end of the
5645 // container if the instance is created without specific insertion point.
5646 if (ForceMoveToEndOfContainer()) {
5647 pointToInsert
= NextInsertionPointRef();
5648 MOZ_ASSERT(pointToInsert
.IsSet());
5649 MOZ_ASSERT(movedContentRange
.StartRef().EqualsOrIsBefore(pointToInsert
));
5650 movedContentRange
.SetEnd(pointToInsert
);
5651 MOZ_LOG(gOneLineMoverLog
, LogLevel::Debug
,
5652 ("Run: Updated movedContentRange end to next insertion point"));
5654 // And also if pointToInsert has been made invalid with removing preceding
5655 // children, we should move the content to the end of the container.
5656 else if (aHTMLEditor
.MayHaveMutationEventListeners() &&
5657 MOZ_UNLIKELY(!moveContentsInLineResult
.NextInsertionPointRef()
5658 .IsSetAndValid())) {
5659 mPointToInsert
.SetToEndOf(mPointToInsert
.GetContainer());
5660 pointToInsert
= NextInsertionPointRef();
5661 movedContentRange
.SetEnd(pointToInsert
);
5662 MOZ_LOG(gOneLineMoverLog
, LogLevel::Debug
,
5663 ("Run: Updated mPointToInsert to end of container and updated "
5664 "movedContentRange"));
5666 MOZ_DIAGNOSTIC_ASSERT(
5667 moveContentsInLineResult
.NextInsertionPointRef().IsSet());
5668 mPointToInsert
= moveContentsInLineResult
.NextInsertionPointRef();
5669 pointToInsert
= NextInsertionPointRef();
5670 if (!aHTMLEditor
.MayHaveMutationEventListeners() ||
5671 movedContentRange
.EndRef().IsBefore(pointToInsert
)) {
5672 MOZ_ASSERT(pointToInsert
.IsSet());
5674 movedContentRange
.StartRef().EqualsOrIsBefore(pointToInsert
));
5675 movedContentRange
.SetEnd(pointToInsert
);
5676 MOZ_LOG(gOneLineMoverLog
, LogLevel::Debug
,
5677 ("Run: Updated mPointToInsert and updated movedContentRange"));
5679 MOZ_LOG(gOneLineMoverLog
, LogLevel::Debug
,
5680 ("Run: Updated only mPointToInsert"));
5685 // Nothing has been moved, we don't need to clean up unnecessary <br> element.
5686 // And also if we're not moving content into a block, we can quit right now.
5687 if (moveContentsInLineResult
.Ignored() ||
5688 MOZ_UNLIKELY(!mDestInclusiveAncestorBlock
)) {
5689 MOZ_LOG(gOneLineMoverLog
, LogLevel::Info
,
5690 (moveContentsInLineResult
.Ignored()
5691 ? "Run: Did nothing for any children"
5692 : "Run: Finished (not dest block)"));
5693 return moveContentsInLineResult
;
5696 // If we couldn't track the range to clean up, we should just stop cleaning up
5697 // because returning error from here may change the behavior of web apps using
5698 // mutation event listeners.
5699 if (MOZ_UNLIKELY(!movedContentRange
.IsPositioned() ||
5700 movedContentRange
.Collapsed())) {
5701 MOZ_LOG(gOneLineMoverLog
, LogLevel::Info
,
5702 (!movedContentRange
.IsPositioned()
5703 ? "Run: Finished (Couldn't track moved line)"
5704 : "Run: Finished (Moved line was empty)"));
5705 return moveContentsInLineResult
;
5708 nsresult rv
= DeleteUnnecessaryTrailingLineBreakInMovedLineEnd(
5709 aHTMLEditor
, movedContentRange
, aEditingHost
);
5710 if (NS_FAILED(rv
)) {
5712 "AutoMoveOneLineHandler::"
5713 "DeleteUnnecessaryTrailingLineBreakInMovedLineEnd() failed");
5714 MOZ_LOG(gOneLineMoverLog
, LogLevel::Error
,
5715 ("Run: DeleteUnnecessaryTrailingLineBreakInMovedLineEnd() failed"));
5716 moveContentsInLineResult
.IgnoreCaretPointSuggestion();
5720 MOZ_LOG(gOneLineMoverLog
, LogLevel::Info
, ("Run: Finished"));
5721 return moveContentsInLineResult
;
5724 nsresult
HTMLEditor::AutoMoveOneLineHandler::
5725 DeleteUnnecessaryTrailingLineBreakInMovedLineEnd(
5726 HTMLEditor
& aHTMLEditor
, const EditorDOMRange
& aMovedContentRange
,
5727 const Element
& aEditingHost
) const {
5728 MOZ_ASSERT(mDestInclusiveAncestorBlock
);
5729 MOZ_ASSERT(aMovedContentRange
.IsPositioned());
5730 MOZ_ASSERT(!aMovedContentRange
.Collapsed());
5732 // If we didn't preserve white-space for backward compatibility and
5733 // white-space becomes not preformatted, we need to clean it up the last text
5734 // node if it ends with a preformatted line break.
5735 if (mPreserveWhiteSpaceStyle
== PreserveWhiteSpaceStyle::No
) {
5736 const RefPtr
<Text
> textNodeEndingWithUnnecessaryLineBreak
= [&]() -> Text
* {
5737 Text
* lastTextNode
= Text::FromNodeOrNull(
5738 mMovingToParentBlock
5739 ? HTMLEditUtils::GetPreviousContent(
5740 *mTopmostSrcAncestorBlockInDestBlock
,
5741 {WalkTreeOption::StopAtBlockBoundary
},
5742 BlockInlineCheck::UseComputedDisplayOutsideStyle
,
5743 mDestInclusiveAncestorBlock
)
5744 : HTMLEditUtils::GetLastLeafContent(
5745 *mDestInclusiveAncestorBlock
,
5746 {LeafNodeType::LeafNodeOrNonEditableNode
}));
5747 if (!lastTextNode
||
5748 !HTMLEditUtils::IsSimplyEditableNode(*lastTextNode
)) {
5751 const nsTextFragment
& textFragment
= lastTextNode
->TextFragment();
5752 const char16_t lastCh
=
5753 textFragment
.GetLength()
5754 ? textFragment
.CharAt(textFragment
.GetLength() - 1u)
5756 return lastCh
== HTMLEditUtils::kNewLine
&&
5757 !EditorUtils::IsNewLinePreformatted(*lastTextNode
)
5761 if (textNodeEndingWithUnnecessaryLineBreak
) {
5762 if (textNodeEndingWithUnnecessaryLineBreak
->TextDataLength() == 1u) {
5763 const RefPtr
<Element
> inlineElement
=
5764 HTMLEditUtils::GetMostDistantAncestorEditableEmptyInlineElement(
5765 *textNodeEndingWithUnnecessaryLineBreak
,
5766 BlockInlineCheck::UseComputedDisplayOutsideStyle
,
5768 nsresult rv
= aHTMLEditor
.DeleteNodeWithTransaction(
5769 inlineElement
? static_cast<nsIContent
&>(*inlineElement
)
5770 : static_cast<nsIContent
&>(
5771 *textNodeEndingWithUnnecessaryLineBreak
));
5772 if (NS_FAILED(rv
)) {
5773 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
5777 Result
<CaretPoint
, nsresult
> caretPointOrError
=
5778 aHTMLEditor
.DeleteTextWithTransaction(
5779 *textNodeEndingWithUnnecessaryLineBreak
,
5780 textNodeEndingWithUnnecessaryLineBreak
->TextDataLength() - 1u,
5782 if (MOZ_UNLIKELY(caretPointOrError
.isErr())) {
5783 NS_WARNING("HTMLEditor::DeleteTextWithTransaction() failed");
5784 return caretPointOrError
.propagateErr();
5786 nsresult rv
= caretPointOrError
.inspect().SuggestCaretPointTo(
5787 aHTMLEditor
, {SuggestCaret::OnlyIfHasSuggestion
,
5788 SuggestCaret::OnlyIfTransactionsAllowedToDoIt
,
5789 SuggestCaret::AndIgnoreTrivialError
});
5790 if (NS_FAILED(rv
)) {
5791 NS_WARNING("CaretPoint::SuggestCaretPointTo() failed");
5794 NS_WARNING_ASSERTION(
5795 rv
!= NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR
,
5796 "CaretPoint::SuggestCaretPointTo() failed, but ignored");
5801 nsCOMPtr
<nsIContent
> lastLineBreakContent
=
5802 mMovingToParentBlock
5803 ? HTMLEditUtils::GetUnnecessaryLineBreakContent(
5804 *mTopmostSrcAncestorBlockInDestBlock
,
5805 ScanLineBreak::BeforeBlock
)
5806 : HTMLEditUtils::GetUnnecessaryLineBreakContent(
5807 *mDestInclusiveAncestorBlock
, ScanLineBreak::AtEndOfBlock
);
5808 if (!lastLineBreakContent
) {
5811 EditorRawDOMPoint
atUnnecessaryLineBreak(lastLineBreakContent
);
5812 if (NS_WARN_IF(!atUnnecessaryLineBreak
.IsSet())) {
5813 return NS_ERROR_FAILURE
;
5815 // If the found unnecessary line break is not what we moved above, we
5816 // shouldn't remove it. E.g., the web app may have inserted it intentionally.
5817 MOZ_ASSERT(aMovedContentRange
.StartRef().IsSetAndValid());
5818 MOZ_ASSERT(aMovedContentRange
.EndRef().IsSetAndValid());
5819 if (!aMovedContentRange
.Contains(atUnnecessaryLineBreak
)) {
5823 AutoTransactionsConserveSelection
dontChangeMySelection(aHTMLEditor
);
5824 // If it's a text node and ending with a preformatted line break, we should
5826 if (Text
* textNode
= Text::FromNode(lastLineBreakContent
)) {
5827 MOZ_ASSERT(EditorUtils::IsNewLinePreformatted(*textNode
));
5828 if (textNode
->TextDataLength() > 1) {
5829 Result
<CaretPoint
, nsresult
> caretPointOrError
=
5830 aHTMLEditor
.DeleteTextWithTransaction(
5831 MOZ_KnownLive(*textNode
), textNode
->TextDataLength() - 1u, 1u);
5832 if (MOZ_UNLIKELY(caretPointOrError
.isErr())) {
5833 NS_WARNING("HTMLEditor::DeleteTextWithTransaction() failed");
5834 return caretPointOrError
.unwrapErr();
5836 // IgnoreCaretPointSuggestion() because of dontChangeMySelection above.
5837 caretPointOrError
.unwrap().IgnoreCaretPointSuggestion();
5841 MOZ_ASSERT(lastLineBreakContent
->IsHTMLElement(nsGkAtoms::br
));
5843 // If last line break content is the only content of its inline parent, we
5844 // should remove the parent too.
5845 if (const RefPtr
<Element
> inlineElement
=
5846 HTMLEditUtils::GetMostDistantAncestorEditableEmptyInlineElement(
5847 *lastLineBreakContent
,
5848 BlockInlineCheck::UseComputedDisplayOutsideStyle
,
5850 nsresult rv
= aHTMLEditor
.DeleteNodeWithTransaction(*inlineElement
);
5851 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
5852 "EditorBase::DeleteNodeWithTransaction() failed");
5855 // Or if the text node has only the preformatted line break or <br> element,
5856 // we should remove it.
5857 nsresult rv
= aHTMLEditor
.DeleteNodeWithTransaction(*lastLineBreakContent
);
5858 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
5859 "EditorBase::DeleteNodeWithTransaction() failed");
5863 Result
<bool, nsresult
> HTMLEditor::CanMoveNodeOrChildren(
5864 const nsIContent
& aContent
, const nsINode
& aNewContainer
) const {
5865 if (HTMLEditUtils::CanNodeContain(aNewContainer
, aContent
)) {
5868 if (aContent
.IsElement()) {
5869 return CanMoveChildren(*aContent
.AsElement(), aNewContainer
);
5874 Result
<MoveNodeResult
, nsresult
> HTMLEditor::MoveNodeOrChildrenWithTransaction(
5875 nsIContent
& aContentToMove
, const EditorDOMPoint
& aPointToInsert
,
5876 PreserveWhiteSpaceStyle aPreserveWhiteSpaceStyle
,
5877 RemoveIfCommentNode aRemoveIfCommentNode
) {
5878 MOZ_ASSERT(IsEditActionDataAvailable());
5879 MOZ_ASSERT(aPointToInsert
.IsInContentNode());
5881 const auto destWhiteSpaceStyles
=
5882 [&]() -> Maybe
<std::pair
<StyleWhiteSpaceCollapse
, StyleTextWrapMode
>> {
5883 if (aPreserveWhiteSpaceStyle
== PreserveWhiteSpaceStyle::No
||
5884 !aPointToInsert
.IsInContentNode()) {
5887 auto styles
= EditorUtils::GetComputedWhiteSpaceStyles(
5888 *aPointToInsert
.ContainerAs
<nsIContent
>());
5889 if (NS_WARN_IF(styles
.isSome() &&
5890 styles
.value().first
==
5891 StyleWhiteSpaceCollapse::PreserveSpaces
)) {
5896 const auto srcWhiteSpaceStyles
=
5897 [&]() -> Maybe
<std::pair
<StyleWhiteSpaceCollapse
, StyleTextWrapMode
>> {
5898 if (aPreserveWhiteSpaceStyle
== PreserveWhiteSpaceStyle::No
) {
5901 auto styles
= EditorUtils::GetComputedWhiteSpaceStyles(aContentToMove
);
5902 if (NS_WARN_IF(styles
.isSome() &&
5903 styles
.value().first
==
5904 StyleWhiteSpaceCollapse::PreserveSpaces
)) {
5909 // Get the `white-space` shorthand form for the given collapse + mode pair.
5910 const auto GetWhiteSpaceStyleValue
=
5911 [](std::pair
<StyleWhiteSpaceCollapse
, StyleTextWrapMode
> aStyles
) {
5912 if (aStyles
.second
== StyleTextWrapMode::Wrap
) {
5913 switch (aStyles
.first
) {
5914 case StyleWhiteSpaceCollapse::Collapse
:
5915 return u
"normal"_ns
;
5916 case StyleWhiteSpaceCollapse::Preserve
:
5917 return u
"pre-wrap"_ns
;
5918 case StyleWhiteSpaceCollapse::PreserveBreaks
:
5919 return u
"pre-line"_ns
;
5920 case StyleWhiteSpaceCollapse::PreserveSpaces
:
5921 return u
"preserve-spaces"_ns
;
5922 case StyleWhiteSpaceCollapse::BreakSpaces
:
5923 return u
"break-spaces"_ns
;
5926 switch (aStyles
.first
) {
5927 case StyleWhiteSpaceCollapse::Collapse
:
5928 return u
"nowrap"_ns
;
5929 case StyleWhiteSpaceCollapse::Preserve
:
5931 case StyleWhiteSpaceCollapse::PreserveBreaks
:
5932 return u
"nowrap preserve-breaks"_ns
;
5933 case StyleWhiteSpaceCollapse::PreserveSpaces
:
5934 return u
"nowrap preserve-spaces"_ns
;
5935 case StyleWhiteSpaceCollapse::BreakSpaces
:
5936 return u
"nowrap break-spaces"_ns
;
5939 MOZ_ASSERT_UNREACHABLE("all values should be handled above!");
5940 return u
"normal"_ns
;
5943 if (aRemoveIfCommentNode
== RemoveIfCommentNode::Yes
&&
5944 aContentToMove
.IsComment()) {
5945 EditorDOMPoint
pointToInsert(aPointToInsert
);
5947 AutoTrackDOMPoint
trackPointToInsert(RangeUpdaterRef(), &pointToInsert
);
5948 nsresult rv
= DeleteNodeWithTransaction(aContentToMove
);
5949 if (NS_FAILED(rv
)) {
5950 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
5954 if (NS_WARN_IF(!pointToInsert
.IsSetAndValid())) {
5955 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
);
5957 return MoveNodeResult::HandledResult(std::move(pointToInsert
));
5960 // Check if this node can go into the destination node
5961 if (HTMLEditUtils::CanNodeContain(*aPointToInsert
.GetContainer(),
5963 EditorDOMPoint
pointToInsert(aPointToInsert
);
5964 // Preserve white-space in the new position with using `style` attribute.
5965 // This is additional path from point of view of our traditional behavior.
5966 // Therefore, ignore errors especially if we got unexpected DOM tree.
5967 if (destWhiteSpaceStyles
.isSome() && srcWhiteSpaceStyles
.isSome() &&
5968 destWhiteSpaceStyles
.value() != srcWhiteSpaceStyles
.value()) {
5969 // Set `white-space` with `style` attribute if it's nsStyledElement.
5970 if (nsStyledElement
* styledElement
=
5971 nsStyledElement::FromNode(&aContentToMove
)) {
5972 DebugOnly
<nsresult
> rvIgnored
=
5973 CSSEditUtils::SetCSSPropertyWithTransaction(
5974 *this, MOZ_KnownLive(*styledElement
), *nsGkAtoms::white_space
,
5975 GetWhiteSpaceStyleValue(srcWhiteSpaceStyles
.value()));
5976 if (NS_WARN_IF(Destroyed())) {
5977 return Err(NS_ERROR_EDITOR_DESTROYED
);
5979 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored
),
5980 "CSSEditUtils::SetCSSPropertyWithTransaction("
5981 "nsGkAtoms::white_space) failed, but ignored");
5983 // Otherwise, if the dest container can have <span> element and <span>
5984 // element can have the moving content node, we should insert it.
5985 else if (HTMLEditUtils::CanNodeContain(*aPointToInsert
.GetContainer(),
5986 *nsGkAtoms::span
) &&
5987 HTMLEditUtils::CanNodeContain(*nsGkAtoms::span
,
5989 RefPtr
<Element
> newSpanElement
= CreateHTMLContent(nsGkAtoms::span
);
5990 if (NS_WARN_IF(!newSpanElement
)) {
5991 return Err(NS_ERROR_FAILURE
);
5993 nsAutoString
styleAttrValue(u
"white-space: "_ns
);
5994 styleAttrValue
.Append(
5995 GetWhiteSpaceStyleValue(srcWhiteSpaceStyles
.value()));
5996 IgnoredErrorResult error
;
5997 newSpanElement
->SetAttr(nsGkAtoms::style
, styleAttrValue
, error
);
5998 NS_WARNING_ASSERTION(!error
.Failed(),
5999 "Element::SetAttr(nsGkAtoms::span) failed");
6000 if (MOZ_LIKELY(!error
.Failed())) {
6001 Result
<CreateElementResult
, nsresult
> insertSpanElementResult
=
6002 InsertNodeWithTransaction
<Element
>(*newSpanElement
,
6004 if (MOZ_UNLIKELY(insertSpanElementResult
.isErr())) {
6005 if (NS_WARN_IF(insertSpanElementResult
.inspectErr() ==
6006 NS_ERROR_EDITOR_DESTROYED
)) {
6007 return Err(NS_ERROR_EDITOR_DESTROYED
);
6010 "HTMLEditor::InsertNodeWithTransaction() failed, but ignored");
6012 // We should move the node into the new <span> to preserve the
6014 pointToInsert
.Set(newSpanElement
, 0u);
6015 // We should put caret after aContentToMove after moving it so that
6016 // we do not need the suggested caret point here.
6017 insertSpanElementResult
.inspect().IgnoreCaretPointSuggestion();
6022 // If it can, move it there.
6023 Result
<MoveNodeResult
, nsresult
> moveNodeResult
=
6024 MoveNodeWithTransaction(aContentToMove
, pointToInsert
);
6025 NS_WARNING_ASSERTION(moveNodeResult
.isOk(),
6026 "HTMLEditor::MoveNodeWithTransaction() failed");
6027 // XXX This is odd to override the handled state here, but stopping this
6028 // hits an NS_ASSERTION in WhiteSpaceVisibilityKeeper::
6029 // MergeFirstLineOfRightBlockElementIntoAncestorLeftBlockElement.
6030 if (moveNodeResult
.isOk()) {
6031 MoveNodeResult unwrappedMoveNodeResult
= moveNodeResult
.unwrap();
6032 unwrappedMoveNodeResult
.MarkAsHandled();
6033 return unwrappedMoveNodeResult
;
6035 return moveNodeResult
;
6038 // If it can't, move its children (if any), and then delete it.
6039 auto moveNodeResult
=
6040 [&]() MOZ_CAN_RUN_SCRIPT
-> Result
<MoveNodeResult
, nsresult
> {
6041 if (!aContentToMove
.IsElement()) {
6042 return MoveNodeResult::HandledResult(aPointToInsert
);
6044 Result
<MoveNodeResult
, nsresult
> moveChildrenResult
=
6045 MoveChildrenWithTransaction(MOZ_KnownLive(*aContentToMove
.AsElement()),
6046 aPointToInsert
, aPreserveWhiteSpaceStyle
,
6047 aRemoveIfCommentNode
);
6048 NS_WARNING_ASSERTION(moveChildrenResult
.isOk(),
6049 "HTMLEditor::MoveChildrenWithTransaction() failed");
6050 return moveChildrenResult
;
6052 if (MOZ_UNLIKELY(moveNodeResult
.isErr())) {
6053 return moveNodeResult
; // Already warned in the lambda.
6056 nsresult rv
= DeleteNodeWithTransaction(aContentToMove
);
6057 if (NS_FAILED(rv
)) {
6058 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
6059 moveNodeResult
.inspect().IgnoreCaretPointSuggestion();
6062 if (!MayHaveMutationEventListeners()) {
6063 return moveNodeResult
;
6065 // Mutation event listener may make `offset` value invalid with
6066 // removing some previous children while we call
6067 // `DeleteNodeWithTransaction()` so that we should adjust it here.
6068 if (moveNodeResult
.inspect().NextInsertionPointRef().IsSetAndValid()) {
6069 return moveNodeResult
;
6071 moveNodeResult
.inspect().IgnoreCaretPointSuggestion();
6072 return MoveNodeResult::HandledResult(
6073 EditorDOMPoint::AtEndOf(*aPointToInsert
.GetContainer()));
6076 Result
<bool, nsresult
> HTMLEditor::CanMoveChildren(
6077 const Element
& aElement
, const nsINode
& aNewContainer
) const {
6078 if (NS_WARN_IF(&aElement
== &aNewContainer
)) {
6079 return Err(NS_ERROR_FAILURE
);
6081 for (nsIContent
* childContent
= aElement
.GetFirstChild(); childContent
;
6082 childContent
= childContent
->GetNextSibling()) {
6083 Result
<bool, nsresult
> result
=
6084 CanMoveNodeOrChildren(*childContent
, aNewContainer
);
6085 if (result
.isErr() || result
.inspect()) {
6092 Result
<MoveNodeResult
, nsresult
> HTMLEditor::MoveChildrenWithTransaction(
6093 Element
& aElement
, const EditorDOMPoint
& aPointToInsert
,
6094 PreserveWhiteSpaceStyle aPreserveWhiteSpaceStyle
,
6095 RemoveIfCommentNode aRemoveIfCommentNode
) {
6096 MOZ_ASSERT(aPointToInsert
.IsSet());
6098 if (NS_WARN_IF(&aElement
== aPointToInsert
.GetContainer())) {
6099 return Err(NS_ERROR_INVALID_ARG
);
6102 MoveNodeResult moveChildrenResult
=
6103 MoveNodeResult::IgnoredResult(aPointToInsert
);
6104 while (aElement
.GetFirstChild()) {
6105 Result
<MoveNodeResult
, nsresult
> moveNodeOrChildrenResult
=
6106 MoveNodeOrChildrenWithTransaction(
6107 MOZ_KnownLive(*aElement
.GetFirstChild()),
6108 moveChildrenResult
.NextInsertionPointRef(),
6109 aPreserveWhiteSpaceStyle
, aRemoveIfCommentNode
);
6110 if (MOZ_UNLIKELY(moveNodeOrChildrenResult
.isErr())) {
6111 NS_WARNING("HTMLEditor::MoveNodeOrChildrenWithTransaction() failed");
6112 moveChildrenResult
.IgnoreCaretPointSuggestion();
6113 return moveNodeOrChildrenResult
;
6115 moveChildrenResult
|= moveNodeOrChildrenResult
.inspect();
6117 return moveChildrenResult
;
6120 nsresult
HTMLEditor::MoveAllChildren(nsINode
& aContainer
,
6121 const EditorRawDOMPoint
& aPointToInsert
) {
6122 if (!aContainer
.HasChildren()) {
6125 nsIContent
* firstChild
= aContainer
.GetFirstChild();
6126 if (NS_WARN_IF(!firstChild
)) {
6127 return NS_ERROR_FAILURE
;
6129 nsIContent
* lastChild
= aContainer
.GetLastChild();
6130 if (NS_WARN_IF(!lastChild
)) {
6131 return NS_ERROR_FAILURE
;
6133 nsresult rv
= MoveChildrenBetween(*firstChild
, *lastChild
, aPointToInsert
);
6134 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
6135 "HTMLEditor::MoveChildrenBetween() failed");
6139 nsresult
HTMLEditor::MoveChildrenBetween(
6140 nsIContent
& aFirstChild
, nsIContent
& aLastChild
,
6141 const EditorRawDOMPoint
& aPointToInsert
) {
6142 nsCOMPtr
<nsINode
> oldContainer
= aFirstChild
.GetParentNode();
6143 if (NS_WARN_IF(oldContainer
!= aLastChild
.GetParentNode()) ||
6144 NS_WARN_IF(!aPointToInsert
.IsInContentNode()) ||
6145 NS_WARN_IF(!aPointToInsert
.CanContainerHaveChildren())) {
6146 return NS_ERROR_INVALID_ARG
;
6149 // First, store all children which should be moved to the new container.
6150 AutoTArray
<nsCOMPtr
<nsIContent
>, 10> children
;
6151 for (nsIContent
* child
= &aFirstChild
; child
;
6152 child
= child
->GetNextSibling()) {
6153 children
.AppendElement(child
);
6154 if (child
== &aLastChild
) {
6159 if (NS_WARN_IF(children
.LastElement() != &aLastChild
)) {
6160 return NS_ERROR_INVALID_ARG
;
6163 nsCOMPtr
<nsIContent
> newContainer
= aPointToInsert
.ContainerAs
<nsIContent
>();
6164 nsCOMPtr
<nsIContent
> nextNode
= aPointToInsert
.GetChild();
6165 IgnoredErrorResult error
;
6166 for (size_t i
= children
.Length(); i
> 0; --i
) {
6167 nsCOMPtr
<nsIContent
>& child
= children
[i
- 1];
6168 if (child
->GetParentNode() != oldContainer
) {
6169 // If the child has been moved to different container, we shouldn't
6173 if (NS_WARN_IF(!HTMLEditUtils::IsRemovableNode(*child
))) {
6174 return NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
;
6176 oldContainer
->RemoveChild(*child
, error
);
6177 if (NS_WARN_IF(Destroyed())) {
6178 return NS_ERROR_EDITOR_DESTROYED
;
6180 if (error
.Failed()) {
6181 NS_WARNING("nsINode::RemoveChild() failed");
6182 return error
.StealNSResult();
6185 // If we're not appending the children to the new container, we should
6186 // check if referring next node of insertion point is still in the new
6188 EditorRawDOMPoint
pointToInsert(nextNode
);
6189 if (NS_WARN_IF(!pointToInsert
.IsSet()) ||
6190 NS_WARN_IF(pointToInsert
.GetContainer() != newContainer
)) {
6191 // The next node of insertion point has been moved by mutation observer.
6192 // Let's stop moving the remaining nodes.
6193 // XXX Or should we move remaining children after the last moved child?
6194 return NS_ERROR_FAILURE
;
6198 !EditorUtils::IsEditableContent(*newContainer
, EditorType::HTML
))) {
6199 return NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
;
6201 newContainer
->InsertBefore(*child
, nextNode
, error
);
6202 if (NS_WARN_IF(Destroyed())) {
6203 return NS_ERROR_EDITOR_DESTROYED
;
6205 if (error
.Failed()) {
6206 NS_WARNING("nsINode::InsertBefore() failed");
6207 return error
.StealNSResult();
6209 // If the child was inserted or appended properly, the following children
6210 // should be inserted before it. Otherwise, keep using current position.
6211 if (child
->GetParentNode() == newContainer
) {
6218 nsresult
HTMLEditor::MovePreviousSiblings(
6219 nsIContent
& aChild
, const EditorRawDOMPoint
& aPointToInsert
) {
6220 if (NS_WARN_IF(!aChild
.GetParentNode())) {
6221 return NS_ERROR_INVALID_ARG
;
6223 nsIContent
* firstChild
= aChild
.GetParentNode()->GetFirstChild();
6224 if (NS_WARN_IF(!firstChild
)) {
6225 return NS_ERROR_FAILURE
;
6227 nsIContent
* lastChild
=
6228 &aChild
== firstChild
? firstChild
: aChild
.GetPreviousSibling();
6229 if (NS_WARN_IF(!lastChild
)) {
6230 return NS_ERROR_FAILURE
;
6232 nsresult rv
= MoveChildrenBetween(*firstChild
, *lastChild
, aPointToInsert
);
6233 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
6234 "HTMLEditor::MoveChildrenBetween() failed");
6238 nsresult
HTMLEditor::MoveInclusiveNextSiblings(
6239 nsIContent
& aChild
, const EditorRawDOMPoint
& aPointToInsert
) {
6240 if (NS_WARN_IF(!aChild
.GetParentNode())) {
6241 return NS_ERROR_INVALID_ARG
;
6243 nsIContent
* lastChild
= aChild
.GetParentNode()->GetLastChild();
6244 if (NS_WARN_IF(!lastChild
)) {
6245 return NS_ERROR_FAILURE
;
6247 nsresult rv
= MoveChildrenBetween(aChild
, *lastChild
, aPointToInsert
);
6248 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
6249 "HTMLEditor::MoveChildrenBetween() failed");
6253 nsresult
HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
6254 DeleteContentButKeepTableStructure(HTMLEditor
& aHTMLEditor
,
6255 nsIContent
& aContent
) {
6256 MOZ_ASSERT(aHTMLEditor
.IsEditActionDataAvailable());
6258 if (!HTMLEditUtils::IsAnyTableElementButNotTable(&aContent
)) {
6259 nsresult rv
= aHTMLEditor
.DeleteNodeWithTransaction(aContent
);
6260 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
6261 "EditorBase::DeleteNodeWithTransaction() failed");
6265 // XXX For performance, this should just call
6266 // DeleteContentButKeepTableStructure() while there are children in
6267 // aContent. If we need to avoid infinite loop because mutation event
6268 // listeners can add unexpected nodes into aContent, we should just loop
6269 // only original count of the children.
6270 AutoTArray
<OwningNonNull
<nsIContent
>, 10> childList
;
6271 for (nsIContent
* child
= aContent
.GetFirstChild(); child
;
6272 child
= child
->GetNextSibling()) {
6273 childList
.AppendElement(*child
);
6276 for (const auto& child
: childList
) {
6277 // MOZ_KnownLive because 'childList' is guaranteed to
6280 DeleteContentButKeepTableStructure(aHTMLEditor
, MOZ_KnownLive(child
));
6281 if (NS_FAILED(rv
)) {
6282 NS_WARNING("HTMLEditor::DeleteContentButKeepTableStructure() failed");
6289 nsresult
HTMLEditor::DeleteMostAncestorMailCiteElementIfEmpty(
6290 nsIContent
& aContent
) {
6291 MOZ_ASSERT(IsEditActionDataAvailable());
6293 // The element must be `<blockquote type="cite">` or
6294 // `<span _moz_quote="true">`.
6295 RefPtr
<Element
> mailCiteElement
=
6296 GetMostDistantAncestorMailCiteElement(aContent
);
6297 if (!mailCiteElement
) {
6300 bool seenBR
= false;
6301 if (!HTMLEditUtils::IsEmptyNode(
6303 {EmptyCheckOption::TreatListItemAsVisible
,
6304 EmptyCheckOption::TreatTableCellAsVisible
,
6305 EmptyCheckOption::TreatNonEditableContentAsInvisible
},
6309 EditorDOMPoint
atEmptyMailCiteElement(mailCiteElement
);
6311 AutoEditorDOMPointChildInvalidator
lockOffset(atEmptyMailCiteElement
);
6312 nsresult rv
= DeleteNodeWithTransaction(*mailCiteElement
);
6313 if (NS_FAILED(rv
)) {
6314 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
6319 if (!atEmptyMailCiteElement
.IsSet() || !seenBR
) {
6320 NS_WARNING_ASSERTION(
6321 atEmptyMailCiteElement
.IsSet(),
6322 "Mutation event listener might changed the DOM tree during "
6323 "EditorBase::DeleteNodeWithTransaction(), but ignored");
6327 Result
<CreateElementResult
, nsresult
> insertBRElementResult
=
6328 InsertBRElement(WithTransaction::Yes
, atEmptyMailCiteElement
);
6329 if (MOZ_UNLIKELY(insertBRElementResult
.isErr())) {
6330 NS_WARNING("HTMLEditor::InsertBRElement(WithTransaction::Yes) failed");
6331 return insertBRElementResult
.unwrapErr();
6333 MOZ_ASSERT(insertBRElementResult
.inspect().GetNewNode());
6334 insertBRElementResult
.inspect().IgnoreCaretPointSuggestion();
6335 nsresult rv
= CollapseSelectionTo(
6336 EditorRawDOMPoint(insertBRElementResult
.inspect().GetNewNode()));
6337 if (NS_WARN_IF(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
6338 return NS_ERROR_EDITOR_DESTROYED
;
6340 NS_WARNING_ASSERTION(
6342 "EditorBase::::CollapseSelectionTo() failed, but ignored");
6346 Element
* HTMLEditor::AutoDeleteRangesHandler::AutoEmptyBlockAncestorDeleter::
6347 ScanEmptyBlockInclusiveAncestor(const HTMLEditor
& aHTMLEditor
,
6348 nsIContent
& aStartContent
) {
6349 MOZ_ASSERT(aHTMLEditor
.IsEditActionDataAvailable());
6350 MOZ_ASSERT(!mEmptyInclusiveAncestorBlockElement
);
6352 // If we are inside an empty block, delete it.
6353 // Note: do NOT delete table elements this way.
6354 // Note: do NOT delete non-editable block element.
6355 Element
* editableBlockElement
= HTMLEditUtils::GetInclusiveAncestorElement(
6356 aStartContent
, HTMLEditUtils::ClosestEditableBlockElement
,
6357 BlockInlineCheck::UseComputedDisplayOutsideStyle
);
6358 if (!editableBlockElement
) {
6361 // XXX Perhaps, this is slow loop. If empty blocks are nested, then,
6362 // each block checks whether it's empty or not. However, descendant
6363 // blocks are checked again and again by IsEmptyNode(). Perhaps, it
6364 // should be able to take "known empty element" for avoiding same checks.
6365 while (editableBlockElement
&&
6366 HTMLEditUtils::IsRemovableFromParentNode(*editableBlockElement
) &&
6367 !HTMLEditUtils::IsAnyTableElement(editableBlockElement
) &&
6368 HTMLEditUtils::IsEmptyNode(*editableBlockElement
)) {
6369 // If the removable empty list item is a child of editing host list element,
6370 // we should not delete it.
6371 if (HTMLEditUtils::IsListItem(editableBlockElement
)) {
6372 Element
* const parentElement
= editableBlockElement
->GetParentElement();
6373 if (parentElement
&& HTMLEditUtils::IsAnyListElement(parentElement
) &&
6374 !HTMLEditUtils::IsRemovableFromParentNode(*parentElement
) &&
6375 HTMLEditUtils::IsEmptyNode(*parentElement
)) {
6379 mEmptyInclusiveAncestorBlockElement
= editableBlockElement
;
6380 editableBlockElement
= HTMLEditUtils::GetAncestorElement(
6381 *mEmptyInclusiveAncestorBlockElement
,
6382 HTMLEditUtils::ClosestEditableBlockElement
,
6383 BlockInlineCheck::UseComputedDisplayOutsideStyle
);
6385 if (!mEmptyInclusiveAncestorBlockElement
) {
6389 // XXX Because of not checking whether found block element is editable
6390 // in the above loop, empty ediable block element may be overwritten
6391 // with empty non-editable clock element. Therefore, we fail to
6392 // remove the found empty nodes.
6393 if (NS_WARN_IF(!mEmptyInclusiveAncestorBlockElement
->IsEditable()) ||
6394 NS_WARN_IF(!mEmptyInclusiveAncestorBlockElement
->GetParentElement())) {
6395 mEmptyInclusiveAncestorBlockElement
= nullptr;
6397 return mEmptyInclusiveAncestorBlockElement
;
6400 nsresult
HTMLEditor::AutoDeleteRangesHandler::AutoEmptyBlockAncestorDeleter::
6401 ComputeTargetRanges(const HTMLEditor
& aHTMLEditor
,
6402 nsIEditor::EDirection aDirectionAndAmount
,
6403 const Element
& aEditingHost
,
6404 AutoRangeArray
& aRangesToDelete
) const {
6405 MOZ_ASSERT(mEmptyInclusiveAncestorBlockElement
);
6407 // We'll delete `mEmptyInclusiveAncestorBlockElement` node from the tree, but
6408 // we should return the range from start/end of next/previous editable content
6409 // to end/start of the element for compatiblity with the other browsers.
6410 switch (aDirectionAndAmount
) {
6411 case nsIEditor::eNone
:
6413 case nsIEditor::ePrevious
:
6414 case nsIEditor::ePreviousWord
:
6415 case nsIEditor::eToBeginningOfLine
: {
6416 EditorRawDOMPoint startPoint
=
6417 HTMLEditUtils::GetPreviousEditablePoint
<EditorRawDOMPoint
>(
6418 *mEmptyInclusiveAncestorBlockElement
, &aEditingHost
,
6419 // In this case, we don't join block elements so that we won't
6420 // delete invisible trailing whitespaces in the previous element.
6421 InvisibleWhiteSpaces::Preserve
,
6422 // In this case, we won't join table cells so that we should
6423 // get a range which is in a table cell even if it's in a
6425 TableBoundary::NoCrossAnyTableElement
);
6426 if (!startPoint
.IsSet()) {
6428 "HTMLEditUtils::GetPreviousEditablePoint() didn't return a valid "
6430 return NS_ERROR_FAILURE
;
6432 nsresult rv
= aRangesToDelete
.SetStartAndEnd(
6434 EditorRawDOMPoint::AtEndOf(mEmptyInclusiveAncestorBlockElement
));
6435 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
6436 "AutoRangeArray::SetStartAndEnd() failed");
6439 case nsIEditor::eNext
:
6440 case nsIEditor::eNextWord
:
6441 case nsIEditor::eToEndOfLine
: {
6442 EditorRawDOMPoint endPoint
=
6443 HTMLEditUtils::GetNextEditablePoint
<EditorRawDOMPoint
>(
6444 *mEmptyInclusiveAncestorBlockElement
, &aEditingHost
,
6445 // In this case, we don't join block elements so that we won't
6446 // delete invisible trailing whitespaces in the next element.
6447 InvisibleWhiteSpaces::Preserve
,
6448 // In this case, we won't join table cells so that we should
6449 // get a range which is in a table cell even if it's in a
6451 TableBoundary::NoCrossAnyTableElement
);
6452 if (!endPoint
.IsSet()) {
6454 "HTMLEditUtils::GetNextEditablePoint() didn't return a valid "
6456 return NS_ERROR_FAILURE
;
6458 nsresult rv
= aRangesToDelete
.SetStartAndEnd(
6459 EditorRawDOMPoint(mEmptyInclusiveAncestorBlockElement
, 0), endPoint
);
6460 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
6461 "AutoRangeArray::SetStartAndEnd() failed");
6465 MOZ_ASSERT_UNREACHABLE("Handle the nsIEditor::EDirection value");
6468 // No direction, let's select the element to be deleted.
6470 aRangesToDelete
.SelectNode(*mEmptyInclusiveAncestorBlockElement
);
6471 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
), "AutoRangeArray::SelectNode() failed");
6475 Result
<RefPtr
<Element
>, nsresult
>
6476 HTMLEditor::AutoDeleteRangesHandler::AutoEmptyBlockAncestorDeleter::
6477 MaybeInsertBRElementBeforeEmptyListItemElement(HTMLEditor
& aHTMLEditor
) {
6478 MOZ_ASSERT(mEmptyInclusiveAncestorBlockElement
);
6479 MOZ_ASSERT(mEmptyInclusiveAncestorBlockElement
->GetParentElement());
6480 MOZ_ASSERT(HTMLEditUtils::IsListItem(mEmptyInclusiveAncestorBlockElement
));
6482 // If the found empty block is a list item element and its grand parent
6483 // (i.e., parent of list element) is NOT a list element, insert <br>
6484 // element before the list element which has the empty list item.
6485 // This odd list structure may occur if `Document.execCommand("indent")`
6486 // is performed for list items.
6487 // XXX Chrome does not remove empty list elements when last content in
6488 // last list item is deleted. We should follow it since current
6489 // behavior is annoying when you type new list item with selecting
6491 if (!HTMLEditUtils::IsFirstChild(*mEmptyInclusiveAncestorBlockElement
,
6492 {WalkTreeOption::IgnoreNonEditableNode
})) {
6493 return RefPtr
<Element
>();
6496 EditorDOMPoint
atParentOfEmptyListItem(
6497 mEmptyInclusiveAncestorBlockElement
->GetParentElement());
6498 if (NS_WARN_IF(!atParentOfEmptyListItem
.IsSet())) {
6499 return Err(NS_ERROR_FAILURE
);
6501 if (HTMLEditUtils::IsAnyListElement(atParentOfEmptyListItem
.GetContainer())) {
6502 return RefPtr
<Element
>();
6504 Result
<CreateElementResult
, nsresult
> insertBRElementResult
=
6505 aHTMLEditor
.InsertBRElement(WithTransaction::Yes
,
6506 atParentOfEmptyListItem
);
6507 if (MOZ_UNLIKELY(insertBRElementResult
.isErr())) {
6508 NS_WARNING("HTMLEditor::InsertBRElement(WithTransaction::Yes) failed");
6509 return insertBRElementResult
.propagateErr();
6511 CreateElementResult unwrappedInsertBRElementResult
=
6512 insertBRElementResult
.unwrap();
6513 nsresult rv
= unwrappedInsertBRElementResult
.SuggestCaretPointTo(
6514 aHTMLEditor
, {SuggestCaret::OnlyIfHasSuggestion
,
6515 SuggestCaret::OnlyIfTransactionsAllowedToDoIt
,
6516 SuggestCaret::AndIgnoreTrivialError
});
6517 if (NS_FAILED(rv
)) {
6518 NS_WARNING("CreateElementResult::SuggestCaretPointTo() failed");
6521 MOZ_ASSERT(unwrappedInsertBRElementResult
.GetNewNode());
6522 return unwrappedInsertBRElementResult
.UnwrapNewNode();
6525 Result
<CaretPoint
, nsresult
> HTMLEditor::AutoDeleteRangesHandler::
6526 AutoEmptyBlockAncestorDeleter::GetNewCaretPosition(
6527 const HTMLEditor
& aHTMLEditor
,
6528 nsIEditor::EDirection aDirectionAndAmount
) const {
6529 MOZ_ASSERT(mEmptyInclusiveAncestorBlockElement
);
6530 MOZ_ASSERT(mEmptyInclusiveAncestorBlockElement
->GetParentElement());
6531 MOZ_ASSERT(aHTMLEditor
.IsEditActionDataAvailable());
6533 switch (aDirectionAndAmount
) {
6534 case nsIEditor::eNext
:
6535 case nsIEditor::eNextWord
:
6536 case nsIEditor::eToEndOfLine
: {
6537 // Collapse Selection to next node of after empty block element
6538 // if there is. Otherwise, to just after the empty block.
6539 auto afterEmptyBlock(
6540 EditorDOMPoint::After(mEmptyInclusiveAncestorBlockElement
));
6541 MOZ_ASSERT(afterEmptyBlock
.IsSet());
6542 if (nsIContent
* nextContentOfEmptyBlock
= HTMLEditUtils::GetNextContent(
6543 afterEmptyBlock
, {}, BlockInlineCheck::Unused
,
6544 aHTMLEditor
.ComputeEditingHost())) {
6545 EditorDOMPoint pt
= HTMLEditUtils::GetGoodCaretPointFor
<EditorDOMPoint
>(
6546 *nextContentOfEmptyBlock
, aDirectionAndAmount
);
6548 NS_WARNING("HTMLEditUtils::GetGoodCaretPointFor() failed");
6549 return Err(NS_ERROR_FAILURE
);
6551 return CaretPoint(std::move(pt
));
6553 if (NS_WARN_IF(!afterEmptyBlock
.IsSet())) {
6554 return Err(NS_ERROR_FAILURE
);
6556 return CaretPoint(std::move(afterEmptyBlock
));
6558 case nsIEditor::ePrevious
:
6559 case nsIEditor::ePreviousWord
:
6560 case nsIEditor::eToBeginningOfLine
: {
6561 // Collapse Selection to previous editable node of the empty block
6562 // if there is. Otherwise, to after the empty block.
6563 EditorRawDOMPoint
atEmptyBlock(mEmptyInclusiveAncestorBlockElement
);
6564 if (nsIContent
* previousContentOfEmptyBlock
=
6565 HTMLEditUtils::GetPreviousContent(
6566 atEmptyBlock
, {WalkTreeOption::IgnoreNonEditableNode
},
6567 BlockInlineCheck::Unused
, aHTMLEditor
.ComputeEditingHost())) {
6568 EditorDOMPoint pt
= HTMLEditUtils::GetGoodCaretPointFor
<EditorDOMPoint
>(
6569 *previousContentOfEmptyBlock
, aDirectionAndAmount
);
6571 NS_WARNING("HTMLEditUtils::GetGoodCaretPointFor() failed");
6572 return Err(NS_ERROR_FAILURE
);
6574 return CaretPoint(std::move(pt
));
6576 auto afterEmptyBlock
=
6577 EditorDOMPoint::After(*mEmptyInclusiveAncestorBlockElement
);
6578 if (NS_WARN_IF(!afterEmptyBlock
.IsSet())) {
6579 return Err(NS_ERROR_FAILURE
);
6581 return CaretPoint(std::move(afterEmptyBlock
));
6583 case nsIEditor::eNone
: {
6584 // Collapse selection at the removing block when we are replacing
6585 // selected content.
6586 EditorDOMPoint
atEmptyBlock(mEmptyInclusiveAncestorBlockElement
);
6587 if (NS_WARN_IF(!atEmptyBlock
.IsSet())) {
6588 return Err(NS_ERROR_FAILURE
);
6590 return CaretPoint(std::move(atEmptyBlock
));
6594 "AutoEmptyBlockAncestorDeleter doesn't support this action yet");
6595 return Err(NS_ERROR_FAILURE
);
6599 Result
<EditActionResult
, nsresult
>
6600 HTMLEditor::AutoDeleteRangesHandler::AutoEmptyBlockAncestorDeleter::Run(
6601 HTMLEditor
& aHTMLEditor
, nsIEditor::EDirection aDirectionAndAmount
) {
6602 MOZ_ASSERT(mEmptyInclusiveAncestorBlockElement
);
6603 MOZ_ASSERT(mEmptyInclusiveAncestorBlockElement
->GetParentElement());
6604 MOZ_ASSERT(aHTMLEditor
.IsEditActionDataAvailable());
6607 Result
<EditActionResult
, nsresult
> result
=
6608 MaybeReplaceSubListWithNewListItem(aHTMLEditor
);
6609 if (MOZ_UNLIKELY(result
.isErr())) {
6611 "AutoEmptyBlockAncestorDeleter::MaybeReplaceSubListWithNewListItem() "
6615 if (result
.inspect().Handled()) {
6620 if (HTMLEditUtils::IsListItem(mEmptyInclusiveAncestorBlockElement
)) {
6621 Result
<RefPtr
<Element
>, nsresult
> result
=
6622 MaybeInsertBRElementBeforeEmptyListItemElement(aHTMLEditor
);
6623 if (MOZ_UNLIKELY(result
.isErr())) {
6625 "AutoEmptyBlockAncestorDeleter::"
6626 "MaybeInsertBRElementBeforeEmptyListItemElement() failed");
6627 return result
.propagateErr();
6629 // If a `<br>` element is inserted, caret should be moved to after it.
6630 if (RefPtr
<Element
> brElement
= result
.unwrap()) {
6632 aHTMLEditor
.CollapseSelectionTo(EditorRawDOMPoint(brElement
));
6633 if (NS_FAILED(rv
)) {
6634 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
6635 "EditorBase::CollapseSelectionTo() failed");
6640 Result
<CaretPoint
, nsresult
> result
=
6641 GetNewCaretPosition(aHTMLEditor
, aDirectionAndAmount
);
6642 if (MOZ_UNLIKELY(result
.isErr())) {
6643 NS_WARNING("AutoEmptyBlockAncestorDeleter::GetNewCaretPosition() failed");
6644 return result
.propagateErr();
6646 MOZ_ASSERT(result
.inspect().HasCaretPointSuggestion());
6647 nsresult rv
= result
.inspect().SuggestCaretPointTo(aHTMLEditor
, {});
6648 if (NS_FAILED(rv
)) {
6649 NS_WARNING("CaretPoint::SuggestCaretPointTo() failed");
6653 nsresult rv
= aHTMLEditor
.DeleteNodeWithTransaction(
6654 MOZ_KnownLive(*mEmptyInclusiveAncestorBlockElement
));
6655 if (NS_FAILED(rv
)) {
6656 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
6659 return EditActionResult::HandledResult();
6662 Result
<EditActionResult
, nsresult
> HTMLEditor::AutoDeleteRangesHandler::
6663 AutoEmptyBlockAncestorDeleter::MaybeReplaceSubListWithNewListItem(
6664 HTMLEditor
& aHTMLEditor
) {
6665 // If we're deleting sublist element and it's the last list item of its parent
6666 // list, we should replace it with a list element.
6667 if (!HTMLEditUtils::IsAnyListElement(mEmptyInclusiveAncestorBlockElement
)) {
6668 return EditActionResult::IgnoredResult();
6670 RefPtr
<Element
> parentElement
=
6671 mEmptyInclusiveAncestorBlockElement
->GetParentElement();
6672 if (!parentElement
|| !HTMLEditUtils::IsAnyListElement(parentElement
) ||
6673 !HTMLEditUtils::IsEmptyNode(
6675 {EmptyCheckOption::TreatNonEditableContentAsInvisible
})) {
6676 return EditActionResult::IgnoredResult();
6679 nsCOMPtr
<nsINode
> nextSibling
=
6680 mEmptyInclusiveAncestorBlockElement
->GetNextSibling();
6681 nsresult rv
= aHTMLEditor
.DeleteNodeWithTransaction(
6682 MOZ_KnownLive(*mEmptyInclusiveAncestorBlockElement
));
6683 if (NS_FAILED(rv
)) {
6684 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
6687 Result
<CreateElementResult
, nsresult
> insertListItemResult
=
6688 aHTMLEditor
.CreateAndInsertElement(
6689 WithTransaction::Yes
,
6690 parentElement
->IsHTMLElement(nsGkAtoms::dl
) ? *nsGkAtoms::dd
6692 !nextSibling
|| nextSibling
->GetParentNode() != parentElement
6693 ? EditorDOMPoint::AtEndOf(*parentElement
)
6694 : EditorDOMPoint(nextSibling
),
6695 [](HTMLEditor
& aHTMLEditor
, Element
& aNewElement
,
6696 const EditorDOMPoint
& aPointToInsert
) -> nsresult
{
6697 RefPtr
<Element
> brElement
=
6698 aHTMLEditor
.CreateHTMLContent(nsGkAtoms::br
);
6699 if (MOZ_UNLIKELY(!brElement
)) {
6701 "EditorBase::CreateHTMLContent(nsGkAtoms::br) failed, but "
6703 return NS_OK
; // Just gives up to insert <br>
6705 IgnoredErrorResult error
;
6706 aNewElement
.AppendChild(*brElement
, error
);
6707 NS_WARNING_ASSERTION(!error
.Failed(),
6708 "nsINode::AppendChild() failed, but ignored");
6711 if (MOZ_UNLIKELY(insertListItemResult
.isErr())) {
6712 NS_WARNING("HTMLEditor::CreateAndInsertElement() failed");
6713 return insertListItemResult
.propagateErr();
6715 CreateElementResult unwrappedInsertListItemResult
=
6716 insertListItemResult
.unwrap();
6717 unwrappedInsertListItemResult
.IgnoreCaretPointSuggestion();
6718 rv
= aHTMLEditor
.CollapseSelectionTo(
6719 EditorRawDOMPoint(unwrappedInsertListItemResult
.GetNewNode(), 0u));
6720 if (NS_FAILED(rv
)) {
6721 NS_WARNING("EditorBase::CollapseSelectionTo() failed");
6724 return EditActionResult::HandledResult();
6727 template <typename EditorDOMRangeType
>
6728 Result
<EditorRawDOMRange
, nsresult
>
6729 HTMLEditor::AutoDeleteRangesHandler::ExtendOrShrinkRangeToDelete(
6730 const HTMLEditor
& aHTMLEditor
, const nsFrameSelection
* aFrameSelection
,
6731 const EditorDOMRangeType
& aRangeToDelete
) const {
6732 MOZ_ASSERT(aHTMLEditor
.IsEditActionDataAvailable());
6733 MOZ_ASSERT(!aRangeToDelete
.Collapsed());
6734 MOZ_ASSERT(aRangeToDelete
.IsPositioned());
6736 const nsIContent
* commonAncestor
= nsIContent::FromNodeOrNull(
6737 nsContentUtils::GetClosestCommonInclusiveAncestor(
6738 aRangeToDelete
.StartRef().GetContainer(),
6739 aRangeToDelete
.EndRef().GetContainer()));
6740 if (MOZ_UNLIKELY(NS_WARN_IF(!commonAncestor
))) {
6741 return Err(NS_ERROR_FAILURE
);
6744 // Editing host may be nested and outer one could have focus. Let's use
6745 // the closest editing host instead.
6746 const RefPtr
<Element
> closestEditingHost
=
6747 aHTMLEditor
.ComputeEditingHost(*commonAncestor
, LimitInBodyElement::No
);
6748 if (NS_WARN_IF(!closestEditingHost
)) {
6749 return Err(NS_ERROR_FAILURE
);
6752 // Look for the common ancestor's block element in the editing host. It's
6753 // fine that we get non-editable block element which is ancestor of inline
6754 // editing host because the following code checks editing host too.
6755 const RefPtr
<Element
> closestBlockAncestorOrInlineEditingHost
= [&]() {
6756 // Note that if non-closest editing host has focus, found block may be
6758 if (Element
* const maybeEditableBlockElement
=
6759 HTMLEditUtils::GetInclusiveAncestorElement(
6760 *commonAncestor
, HTMLEditUtils::ClosestBlockElement
,
6761 BlockInlineCheck::UseComputedDisplayOutsideStyle
,
6762 closestEditingHost
)) {
6763 return maybeEditableBlockElement
;
6765 return closestEditingHost
.get();
6768 // Set up for loops and cache our root element
6769 // If only one list element is selected, and if the list element is empty,
6770 // we should delete only the list element. Or if the list element is not
6771 // empty, we should make the list has only one empty list item element.
6772 if (const Element
* maybeListElement
=
6773 HTMLEditUtils::GetElementIfOnlyOneSelected(aRangeToDelete
)) {
6774 if (HTMLEditUtils::IsAnyListElement(maybeListElement
) &&
6775 !HTMLEditUtils::IsEmptyAnyListElement(*maybeListElement
)) {
6776 EditorRawDOMRange range
=
6777 HTMLEditUtils::GetRangeSelectingAllContentInAllListItems
<
6778 EditorRawDOMRange
>(*maybeListElement
);
6779 if (range
.IsPositioned()) {
6780 if (EditorUtils::IsEditableContent(
6781 *range
.StartRef().ContainerAs
<nsIContent
>(),
6782 EditorType::HTML
) &&
6783 EditorUtils::IsEditableContent(
6784 *range
.EndRef().ContainerAs
<nsIContent
>(), EditorType::HTML
)) {
6788 // If the first and/or last list item is not editable, we need to do more
6789 // complicated things probably, but we just delete the list element with
6790 // invisible things around it for now since it must be rare case.
6792 // Otherwise, if the list item is empty, we should delete it with invisible
6793 // things around it.
6796 // Find previous visible things before start of selection
6797 EditorRawDOMRange
rangeToDelete(aRangeToDelete
);
6798 if (rangeToDelete
.StartRef().GetContainer() !=
6799 closestBlockAncestorOrInlineEditingHost
) {
6801 WSScanResult backwardScanFromStartResult
=
6802 WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary(
6803 closestEditingHost
, rangeToDelete
.StartRef(),
6804 BlockInlineCheck::UseComputedDisplayOutsideStyle
);
6805 if (!backwardScanFromStartResult
.ReachedCurrentBlockBoundary()) {
6808 MOZ_ASSERT(backwardScanFromStartResult
.GetContent() ==
6809 WSRunScanner(closestEditingHost
, rangeToDelete
.StartRef(),
6810 BlockInlineCheck::UseComputedDisplayOutsideStyle
)
6811 .GetStartReasonContent());
6812 // We want to keep looking up. But stop if we are crossing table
6813 // element boundaries, or if we hit the root.
6814 if (HTMLEditUtils::IsAnyTableElement(
6815 backwardScanFromStartResult
.GetContent()) ||
6816 backwardScanFromStartResult
.GetContent() ==
6817 closestBlockAncestorOrInlineEditingHost
||
6818 backwardScanFromStartResult
.GetContent() == closestEditingHost
) {
6821 // Don't cross list element boundary because we don't want to delete list
6822 // element at start position unless it's empty.
6823 if (HTMLEditUtils::IsAnyListElement(
6824 backwardScanFromStartResult
.GetContent()) &&
6825 !HTMLEditUtils::IsEmptyAnyListElement(
6826 *backwardScanFromStartResult
.ElementPtr())) {
6829 // Don't cross flex-item/grid-item boundary to make new content inserted
6831 if (StaticPrefs::editor_block_inline_check_use_computed_style() &&
6832 backwardScanFromStartResult
.ContentIsElement() &&
6833 HTMLEditUtils::IsFlexOrGridItem(
6834 *backwardScanFromStartResult
.ElementPtr())) {
6837 rangeToDelete
.SetStart(
6838 backwardScanFromStartResult
.PointAtContent
<EditorRawDOMPoint
>());
6840 if (aFrameSelection
&& !aFrameSelection
->IsValidSelectionPoint(
6841 rangeToDelete
.StartRef().GetContainer())) {
6842 NS_WARNING("Computed start container was out of selection limiter");
6843 return Err(NS_ERROR_FAILURE
);
6847 // Expand selection endpoint only if we don't pass an invisible `<br>`, or if
6848 // we really needed to pass that `<br>` (i.e., its block is now totally
6851 // Find next visible things after end of selection
6852 EditorDOMPoint atFirstInvisibleBRElement
;
6853 if (rangeToDelete
.EndRef().GetContainer() !=
6854 closestBlockAncestorOrInlineEditingHost
) {
6856 WSRunScanner
wsScannerAtEnd(
6857 closestEditingHost
, rangeToDelete
.EndRef(),
6858 BlockInlineCheck::UseComputedDisplayOutsideStyle
);
6859 WSScanResult forwardScanFromEndResult
=
6860 wsScannerAtEnd
.ScanNextVisibleNodeOrBlockBoundaryFrom(
6861 rangeToDelete
.EndRef());
6862 if (forwardScanFromEndResult
.ReachedBRElement()) {
6863 // XXX In my understanding, this is odd. The end reason may not be
6864 // same as the reached <br> element because the equality is
6865 // guaranteed only when ReachedCurrentBlockBoundary() returns true.
6866 // However, looks like that this code assumes that
6867 // GetEndReasonContent() returns the (or a) <br> element.
6868 NS_ASSERTION(wsScannerAtEnd
.GetEndReasonContent() ==
6869 forwardScanFromEndResult
.BRElementPtr(),
6870 "End reason is not the reached <br> element");
6871 if (HTMLEditUtils::IsVisibleBRElement(
6872 *wsScannerAtEnd
.GetEndReasonContent())) {
6875 if (!atFirstInvisibleBRElement
.IsSet()) {
6876 atFirstInvisibleBRElement
=
6877 rangeToDelete
.EndRef().To
<EditorDOMPoint
>();
6879 rangeToDelete
.SetEnd(
6880 EditorRawDOMPoint::After(*wsScannerAtEnd
.GetEndReasonContent()));
6884 if (forwardScanFromEndResult
.ReachedCurrentBlockBoundary()) {
6885 MOZ_ASSERT(forwardScanFromEndResult
.GetContent() ==
6886 wsScannerAtEnd
.GetEndReasonContent());
6887 // We want to keep looking up. But stop if we are crossing table
6888 // element boundaries, or if we hit the root.
6889 if (HTMLEditUtils::IsAnyTableElement(
6890 forwardScanFromEndResult
.GetContent()) ||
6891 forwardScanFromEndResult
.GetContent() ==
6892 closestBlockAncestorOrInlineEditingHost
) {
6895 // Don't cross flex-item/grid-item boundary to make new content inserted
6897 if (StaticPrefs::editor_block_inline_check_use_computed_style() &&
6898 forwardScanFromEndResult
.ContentIsElement() &&
6899 HTMLEditUtils::IsFlexOrGridItem(
6900 *forwardScanFromEndResult
.ElementPtr())) {
6903 rangeToDelete
.SetEnd(
6904 forwardScanFromEndResult
.PointAfterContent
<EditorRawDOMPoint
>());
6911 if (aFrameSelection
&& !aFrameSelection
->IsValidSelectionPoint(
6912 rangeToDelete
.EndRef().GetContainer())) {
6913 NS_WARNING("Computed end container was out of selection limiter");
6914 return Err(NS_ERROR_FAILURE
);
6918 // If range boundaries are in list element, and the positions are very
6919 // start/end of first/last list item, we may need to shrink the ranges for
6920 // preventing to remove only all list item elements.
6922 EditorRawDOMRange rangeToDeleteListOrLeaveOneEmptyListItem
=
6923 AutoDeleteRangesHandler::
6924 GetRangeToAvoidDeletingAllListItemsIfSelectingAllOverListElements(
6926 if (rangeToDeleteListOrLeaveOneEmptyListItem
.IsPositioned()) {
6927 rangeToDelete
= std::move(rangeToDeleteListOrLeaveOneEmptyListItem
);
6931 if (atFirstInvisibleBRElement
.IsInContentNode()) {
6932 // Find block node containing invisible `<br>` element.
6933 if (const RefPtr
<const Element
> editableBlockContainingBRElement
=
6934 HTMLEditUtils::GetInclusiveAncestorElement(
6935 *atFirstInvisibleBRElement
.ContainerAs
<nsIContent
>(),
6936 HTMLEditUtils::ClosestEditableBlockElement
,
6937 BlockInlineCheck::UseComputedDisplayOutsideStyle
)) {
6938 if (rangeToDelete
.Contains(
6939 EditorRawDOMPoint(editableBlockContainingBRElement
))) {
6940 return rangeToDelete
;
6942 // Otherwise, the new range should end at the invisible `<br>`.
6943 if (aFrameSelection
&& !aFrameSelection
->IsValidSelectionPoint(
6944 atFirstInvisibleBRElement
.GetContainer())) {
6946 "Computed end container (`<br>` element) was out of selection "
6948 return Err(NS_ERROR_FAILURE
);
6950 rangeToDelete
.SetEnd(atFirstInvisibleBRElement
);
6954 return rangeToDelete
;
6958 EditorRawDOMRange
HTMLEditor::AutoDeleteRangesHandler::
6959 GetRangeToAvoidDeletingAllListItemsIfSelectingAllOverListElements(
6960 const EditorRawDOMRange
& aRangeToDelete
) {
6961 MOZ_ASSERT(aRangeToDelete
.IsPositionedAndValid());
6963 auto GetDeepestEditableStartPointOfList
= [](Element
& aListElement
) {
6964 Element
* const firstListItemElement
=
6965 HTMLEditUtils::GetFirstListItemElement(aListElement
);
6966 if (MOZ_UNLIKELY(!firstListItemElement
)) {
6967 return EditorRawDOMPoint();
6969 if (MOZ_UNLIKELY(!EditorUtils::IsEditableContent(*firstListItemElement
,
6970 EditorType::HTML
))) {
6971 return EditorRawDOMPoint(firstListItemElement
);
6973 return HTMLEditUtils::GetDeepestEditableStartPointOf
<EditorRawDOMPoint
>(
6974 *firstListItemElement
);
6977 auto GetDeepestEditableEndPointOfList
= [](Element
& aListElement
) {
6978 Element
* const lastListItemElement
=
6979 HTMLEditUtils::GetLastListItemElement(aListElement
);
6980 if (MOZ_UNLIKELY(!lastListItemElement
)) {
6981 return EditorRawDOMPoint();
6983 if (MOZ_UNLIKELY(!EditorUtils::IsEditableContent(*lastListItemElement
,
6984 EditorType::HTML
))) {
6985 return EditorRawDOMPoint::After(*lastListItemElement
);
6987 return HTMLEditUtils::GetDeepestEditableEndPointOf
<EditorRawDOMPoint
>(
6988 *lastListItemElement
);
6991 Element
* const startListElement
=
6992 aRangeToDelete
.StartRef().IsInContentNode()
6993 ? HTMLEditUtils::GetClosestInclusiveAncestorAnyListElement(
6994 *aRangeToDelete
.StartRef().ContainerAs
<nsIContent
>())
6996 Element
* const endListElement
=
6997 aRangeToDelete
.EndRef().IsInContentNode()
6998 ? HTMLEditUtils::GetClosestInclusiveAncestorAnyListElement(
6999 *aRangeToDelete
.EndRef().ContainerAs
<nsIContent
>())
7001 if (!startListElement
&& !endListElement
) {
7002 return EditorRawDOMRange();
7005 // FIXME: If there are invalid children, we cannot handle first/last list item
7006 // elements properly. In that case, we should treat list elements and list
7007 // item elements as normal block elements.
7008 if (startListElement
&&
7009 NS_WARN_IF(!HTMLEditUtils::IsValidListElement(
7010 *startListElement
, HTMLEditUtils::TreatSubListElementAs::Valid
))) {
7011 return EditorRawDOMRange();
7013 if (endListElement
&& startListElement
!= endListElement
&&
7014 NS_WARN_IF(!HTMLEditUtils::IsValidListElement(
7015 *endListElement
, HTMLEditUtils::TreatSubListElementAs::Valid
))) {
7016 return EditorRawDOMRange();
7019 const bool startListElementIsEmpty
=
7021 HTMLEditUtils::IsEmptyAnyListElement(*startListElement
);
7022 const bool endListElementIsEmpty
=
7023 startListElement
== endListElement
7024 ? startListElementIsEmpty
7026 HTMLEditUtils::IsEmptyAnyListElement(*endListElement
);
7027 // If both list elements are empty, we should not shrink the range since
7028 // we want to delete the list.
7029 if (startListElementIsEmpty
&& endListElementIsEmpty
) {
7030 return EditorRawDOMRange();
7033 // There may be invisible white-spaces and there are elements in the
7034 // list items. Therefore, we need to compare the deepest positions
7035 // and range boundaries.
7036 EditorRawDOMPoint deepestStartPointOfStartList
=
7037 startListElement
? GetDeepestEditableStartPointOfList(*startListElement
)
7038 : EditorRawDOMPoint();
7039 EditorRawDOMPoint deepestEndPointOfEndList
=
7040 endListElement
? GetDeepestEditableEndPointOfList(*endListElement
)
7041 : EditorRawDOMPoint();
7042 if (MOZ_UNLIKELY(!deepestStartPointOfStartList
.IsSet() &&
7043 !deepestEndPointOfEndList
.IsSet())) {
7044 // FIXME: This does not work well if there is non-list-item contents in the
7045 // list elements. Perhaps, for fixing this invalid cases, we need to wrap
7046 // the content into new list item like Chrome.
7047 return EditorRawDOMRange();
7050 // We don't want to shrink the range into empty sublist.
7051 if (deepestStartPointOfStartList
.IsSet()) {
7052 for (nsIContent
* const maybeList
:
7053 deepestStartPointOfStartList
.GetContainer()
7054 ->InclusiveAncestorsOfType
<nsIContent
>()) {
7055 if (aRangeToDelete
.StartRef().GetContainer() == maybeList
) {
7058 if (HTMLEditUtils::IsAnyListElement(maybeList
) &&
7059 HTMLEditUtils::IsEmptyAnyListElement(*maybeList
->AsElement())) {
7060 deepestStartPointOfStartList
.Set(maybeList
);
7064 if (deepestEndPointOfEndList
.IsSet()) {
7065 for (nsIContent
* const maybeList
:
7066 deepestEndPointOfEndList
.GetContainer()
7067 ->InclusiveAncestorsOfType
<nsIContent
>()) {
7068 if (aRangeToDelete
.EndRef().GetContainer() == maybeList
) {
7071 if (HTMLEditUtils::IsAnyListElement(maybeList
) &&
7072 HTMLEditUtils::IsEmptyAnyListElement(*maybeList
->AsElement())) {
7073 deepestEndPointOfEndList
.SetAfter(maybeList
);
7078 const EditorRawDOMPoint deepestEndPointOfStartList
=
7079 startListElement
? GetDeepestEditableEndPointOfList(*startListElement
)
7080 : EditorRawDOMPoint();
7081 MOZ_ASSERT_IF(deepestStartPointOfStartList
.IsSet(),
7082 deepestEndPointOfStartList
.IsSet());
7083 MOZ_ASSERT_IF(!deepestStartPointOfStartList
.IsSet(),
7084 !deepestEndPointOfStartList
.IsSet());
7086 const bool rangeStartsFromBeginningOfStartList
=
7087 deepestStartPointOfStartList
.IsSet() &&
7088 aRangeToDelete
.StartRef().EqualsOrIsBefore(deepestStartPointOfStartList
);
7089 const bool rangeEndsByEndingOfStartListOrLater
=
7090 !deepestEndPointOfStartList
.IsSet() ||
7091 deepestEndPointOfStartList
.EqualsOrIsBefore(aRangeToDelete
.EndRef());
7092 const bool rangeEndsByEndingOfEndList
=
7093 deepestEndPointOfEndList
.IsSet() &&
7094 deepestEndPointOfEndList
.EqualsOrIsBefore(aRangeToDelete
.EndRef());
7096 EditorRawDOMRange newRangeToDelete
;
7097 // If all over the list element at start boundary is selected, we should
7098 // shrink the range to start from the first list item to avoid to delete
7100 if (!startListElementIsEmpty
&& rangeStartsFromBeginningOfStartList
&&
7101 rangeEndsByEndingOfStartListOrLater
) {
7102 newRangeToDelete
.SetStart(EditorRawDOMPoint(
7103 deepestStartPointOfStartList
.ContainerAs
<nsIContent
>(), 0u));
7105 // If all over the list element at end boundary is selected, and...
7106 if (!endListElementIsEmpty
&& rangeEndsByEndingOfEndList
) {
7107 // If the range starts before the range at end boundary of the range,
7108 // we want to delete the list completely, thus, we should extend the
7109 // range to contain the list element.
7110 if (aRangeToDelete
.StartRef().IsBefore(
7111 EditorRawDOMPoint(endListElement
, 0u))) {
7112 newRangeToDelete
.SetEnd(EditorRawDOMPoint::After(*endListElement
));
7113 MOZ_ASSERT_IF(newRangeToDelete
.StartRef().IsSet(),
7114 newRangeToDelete
.IsPositionedAndValid());
7116 // Otherwise, if the range starts in the end list element, we shouldn't
7117 // delete the list. Therefore, we should shrink the range to end by end
7118 // of the last list item element to avoid to delete all list items.
7120 newRangeToDelete
.SetEnd(EditorRawDOMPoint::AtEndOf(
7121 *deepestEndPointOfEndList
.ContainerAs
<nsIContent
>()));
7122 MOZ_ASSERT_IF(newRangeToDelete
.StartRef().IsSet(),
7123 newRangeToDelete
.IsPositionedAndValid());
7127 if (!newRangeToDelete
.StartRef().IsSet() &&
7128 !newRangeToDelete
.EndRef().IsSet()) {
7129 return EditorRawDOMRange();
7132 if (!newRangeToDelete
.StartRef().IsSet()) {
7133 newRangeToDelete
.SetStart(aRangeToDelete
.StartRef());
7134 MOZ_ASSERT(newRangeToDelete
.IsPositionedAndValid());
7136 if (!newRangeToDelete
.EndRef().IsSet()) {
7137 newRangeToDelete
.SetEnd(aRangeToDelete
.EndRef());
7138 MOZ_ASSERT(newRangeToDelete
.IsPositionedAndValid());
7141 return newRangeToDelete
;
7144 } // namespace mozilla