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