Bug 1686495 [wpt PR 27132] - Add tests for proposed WebDriver Shadow DOM support...
[gecko.git] / editor / libeditor / HTMLEditorDeleteHandler.cpp
blob87f276002dbf9521d576dbc1c2bcbd0933503561
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=2 sw=2 et tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "HTMLEditor.h"
9 #include <algorithm>
10 #include <utility>
12 #include "HTMLEditUtils.h"
13 #include "WSRunObject.h"
15 #include "mozilla/Assertions.h"
16 #include "mozilla/CheckedInt.h"
17 #include "mozilla/ContentIterator.h"
18 #include "mozilla/EditAction.h"
19 #include "mozilla/EditorDOMPoint.h"
20 #include "mozilla/EditorUtils.h"
21 #include "mozilla/InternalMutationEvent.h"
22 #include "mozilla/OwningNonNull.h"
23 #include "mozilla/StaticPrefs_editor.h" // for StaticPrefs::editor_*
24 #include "mozilla/Unused.h"
25 #include "mozilla/dom/Element.h"
26 #include "mozilla/dom/HTMLBRElement.h"
27 #include "mozilla/dom/Selection.h"
28 #include "mozilla/mozalloc.h"
29 #include "nsAString.h"
30 #include "nsAtom.h"
31 #include "nsContentUtils.h"
32 #include "nsDebug.h"
33 #include "nsError.h"
34 #include "nsFrameSelection.h"
35 #include "nsGkAtoms.h"
36 #include "nsIContent.h"
37 #include "nsINode.h"
38 #include "nsRange.h"
39 #include "nsString.h"
40 #include "nsStringFwd.h"
41 #include "nsTArray.h"
43 // NOTE: This file was split from:
44 // https://searchfox.org/mozilla-central/rev/c409dd9235c133ab41eba635f906aa16e050c197/editor/libeditor/HTMLEditSubActionHandler.cpp
46 namespace mozilla {
48 using namespace dom;
49 using InvisibleWhiteSpaces = HTMLEditUtils::InvisibleWhiteSpaces;
50 using StyleDifference = HTMLEditUtils::StyleDifference;
51 using TableBoundary = HTMLEditUtils::TableBoundary;
53 template nsresult HTMLEditor::DeleteTextAndTextNodesWithTransaction(
54 const EditorDOMPoint& aStartPoint, const EditorDOMPoint& aEndPoint,
55 TreatEmptyTextNodes aTreatEmptyTextNodes);
56 template nsresult HTMLEditor::DeleteTextAndTextNodesWithTransaction(
57 const EditorDOMPointInText& aStartPoint,
58 const EditorDOMPointInText& aEndPoint,
59 TreatEmptyTextNodes aTreatEmptyTextNodes);
60 template Result<bool, nsresult> HTMLEditor::CanMoveOrDeleteSomethingInHardLine(
61 const EditorDOMPoint& aPointInHardLine) const;
62 template Result<bool, nsresult> HTMLEditor::CanMoveOrDeleteSomethingInHardLine(
63 const EditorRawDOMPoint& aPointInHardLine) const;
65 /*****************************************************************************
66 * AutoSetTemporaryAncestorLimiter
67 ****************************************************************************/
69 class MOZ_RAII AutoSetTemporaryAncestorLimiter final {
70 public:
71 AutoSetTemporaryAncestorLimiter(const HTMLEditor& aHTMLEditor,
72 Selection& aSelection,
73 nsINode& aStartPointNode,
74 AutoRangeArray* aRanges = nullptr) {
75 MOZ_ASSERT(aSelection.GetType() == SelectionType::eNormal);
77 if (aSelection.GetAncestorLimiter()) {
78 return;
81 Element* root = aHTMLEditor.FindSelectionRoot(&aStartPointNode);
82 if (root) {
83 aHTMLEditor.InitializeSelectionAncestorLimit(*root);
84 mSelection = &aSelection;
85 // Setting ancestor limiter may change ranges which were outer of
86 // the new limiter. Therefore, we need to reinitialize aRanges.
87 if (aRanges) {
88 aRanges->Initialize(aSelection);
93 ~AutoSetTemporaryAncestorLimiter() {
94 if (mSelection) {
95 mSelection->SetAncestorLimiter(nullptr);
99 private:
100 RefPtr<Selection> mSelection;
103 /*****************************************************************************
104 * AutoDeleteRangesHandler
105 ****************************************************************************/
107 class MOZ_STACK_CLASS HTMLEditor::AutoDeleteRangesHandler final {
108 public:
109 explicit AutoDeleteRangesHandler(
110 const AutoDeleteRangesHandler* aParent = nullptr)
111 : mParent(aParent),
112 mOriginalDirectionAndAmount(nsIEditor::eNone),
113 mOriginalStripWrappers(nsIEditor::eNoStrip) {}
116 * ComputeRangesToDelete() computes actual deletion ranges.
118 [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult ComputeRangesToDelete(
119 const HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount,
120 AutoRangeArray& aRangesToDelete);
123 * Deletes content in or around aRangesToDelete.
124 * NOTE: This method creates SelectionBatcher. Therefore, each caller
125 * needs to check if the editor is still available even if this returns
126 * NS_OK.
128 [[nodiscard]] MOZ_CAN_RUN_SCRIPT EditActionResult
129 Run(HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount,
130 nsIEditor::EStripWrappers aStripWrappers,
131 AutoRangeArray& aRangesToDelete);
133 private:
134 bool IsHandlingRecursively() const { return mParent != nullptr; }
136 bool CanFallbackToDeleteRangesWithTransaction(
137 const AutoRangeArray& aRangesToDelete) const {
138 return !IsHandlingRecursively() && !aRangesToDelete.Ranges().IsEmpty() &&
139 (!aRangesToDelete.IsCollapsed() ||
140 EditorBase::HowToHandleCollapsedRangeFor(
141 mOriginalDirectionAndAmount) !=
142 EditorBase::HowToHandleCollapsedRange::Ignore);
146 * HandleDeleteAroundCollapsedRanges() handles deletion with collapsed
147 * ranges. Callers must guarantee that this is called only when
148 * aRangesToDelete.IsCollapsed() returns true.
150 * @param aDirectionAndAmount Direction of the deletion.
151 * @param aStripWrappers Must be eStrip or eNoStrip.
152 * @param aRangesToDelete Ranges to delete. This `IsCollapsed()` must
153 * return true.
154 * @param aWSRunScannerAtCaret Scanner instance which scanned from
155 * caret point.
156 * @param aScanFromCaretPointResult Scan result of aWSRunScannerAtCaret
157 * toward aDirectionAndAmount.
159 [[nodiscard]] MOZ_CAN_RUN_SCRIPT EditActionResult
160 HandleDeleteAroundCollapsedRanges(
161 HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount,
162 nsIEditor::EStripWrappers aStripWrappers, AutoRangeArray& aRangesToDelete,
163 const WSRunScanner& aWSRunScannerAtCaret,
164 const WSScanResult& aScanFromCaretPointResult);
165 nsresult ComputeRangesToDeleteAroundCollapsedRanges(
166 const HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount,
167 AutoRangeArray& aRangesToDelete, const WSRunScanner& aWSRunScannerAtCaret,
168 const WSScanResult& aScanFromCaretPointResult) const;
171 * HandleDeleteNonCollapsedRanges() handles deletion with non-collapsed
172 * ranges. Callers must guarantee that this is called only when
173 * aRangesToDelete.IsCollapsed() returns false.
175 * @param aDirectionAndAmount Direction of the deletion.
176 * @param aStripWrappers Must be eStrip or eNoStrip.
177 * @param aRangesToDelete The ranges to delete.
178 * @param aSelectionWasCollapsed If the caller extended `Selection`
179 * from collapsed, set this to `Yes`.
180 * Otherwise, i.e., `Selection` is not
181 * collapsed from the beginning, set
182 * this to `No`.
184 enum class SelectionWasCollapsed { Yes, No };
185 [[nodiscard]] MOZ_CAN_RUN_SCRIPT EditActionResult
186 HandleDeleteNonCollapsedRanges(HTMLEditor& aHTMLEditor,
187 nsIEditor::EDirection aDirectionAndAmount,
188 nsIEditor::EStripWrappers aStripWrappers,
189 AutoRangeArray& aRangesToDelete,
190 SelectionWasCollapsed aSelectionWasCollapsed);
191 nsresult ComputeRangesToDeleteNonCollapsedRanges(
192 const HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount,
193 AutoRangeArray& aRangesToDelete,
194 SelectionWasCollapsed aSelectionWasCollapsed) const;
197 * HandleDeleteTextAroundCollapsedRanges() handles deletion of collapsed
198 * ranges in a text node.
200 * @param aDirectionAndAmount Must be eNext or ePrevious.
201 * @param aCaretPoisition The position where caret is. This container
202 * must be a text node.
204 [[nodiscard]] MOZ_CAN_RUN_SCRIPT EditActionResult
205 HandleDeleteTextAroundCollapsedRanges(
206 HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount,
207 AutoRangeArray& aRangesToDelete);
208 nsresult ComputeRangesToDeleteTextAroundCollapsedRanges(
209 const HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount,
210 AutoRangeArray& aRangesToDelete) const;
213 * HandleDeleteCollapsedSelectionAtWhiteSpaces() handles deletion of
214 * collapsed selection at white-spaces in a text node.
216 * @param aDirectionAndAmount Direction of the deletion.
217 * @param aPointToDelete The point to delete. I.e., typically, caret
218 * position.
220 [[nodiscard]] MOZ_CAN_RUN_SCRIPT EditActionResult
221 HandleDeleteCollapsedSelectionAtWhiteSpaces(
222 HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount,
223 const EditorDOMPoint& aPointToDelete);
226 * HandleDeleteCollapsedSelectionAtVisibleChar() handles deletion of
227 * collapsed selection in a text node.
229 * @param aDirectionAndAmount Direction of the deletion.
230 * @param aPointToDelete The point in a text node to delete character(s).
231 * Caller must guarantee that this is in a text
232 * node.
234 [[nodiscard]] MOZ_CAN_RUN_SCRIPT EditActionResult
235 HandleDeleteCollapsedSelectionAtVisibleChar(
236 HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount,
237 const EditorDOMPoint& aPointToDelete);
240 * HandleDeleteAtomicContent() handles deletion of atomic elements like
241 * `<br>`, `<hr>`, `<img>`, `<input>`, etc and data nodes except text node
242 * (e.g., comment node). Note that don't call this directly with `<hr>`
243 * element. Instead, call `HandleDeleteHRElement()`. Note that don't call
244 * this for invisible `<br>` element.
246 * @param aAtomicContent The atomic content to be deleted.
247 * @param aCaretPoint The caret point (i.e., selection start or
248 * end).
249 * @param aWSRunScannerAtCaret WSRunScanner instance which was initialized
250 * with the caret point.
252 [[nodiscard]] MOZ_CAN_RUN_SCRIPT EditActionResult
253 HandleDeleteAtomicContent(HTMLEditor& aHTMLEditor, nsIContent& aAtomicContent,
254 const EditorDOMPoint& aCaretPoint,
255 const WSRunScanner& aWSRunScannerAtCaret);
256 nsresult ComputeRangesToDeleteAtomicContent(
257 const HTMLEditor& aHTMLEditor, const nsIContent& aAtomicContent,
258 AutoRangeArray& aRangesToDelete) const;
261 * HandleDeleteHRElement() handles deletion around `<hr>` element. If
262 * aDirectionAndAmount is nsIEditor::ePrevious, aHTElement is removed only
263 * when caret is at next sibling of the `<hr>` element and inter line position
264 * is "left". Otherwise, caret is moved and does not remove the `<hr>`
265 * elemnent.
266 * XXX Perhaps, we can get rid of this special handling because the other
267 * browsers don't do this, and our `<hr>` element handling is really
268 * odd.
270 * @param aDirectionAndAmount Direction of the deletion.
271 * @param aHRElement The `<hr>` element to be removed.
272 * @param aCaretPoint The caret point (i.e., selection start or
273 * end).
274 * @param aWSRunScannerAtCaret WSRunScanner instance which was initialized
275 * with the caret point.
277 [[nodiscard]] MOZ_CAN_RUN_SCRIPT EditActionResult HandleDeleteHRElement(
278 HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount,
279 Element& aHRElement, const EditorDOMPoint& aCaretPoint,
280 const WSRunScanner& aWSRunScannerAtCaret);
281 nsresult ComputeRangesToDeleteHRElement(
282 const HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount,
283 Element& aHRElement, const EditorDOMPoint& aCaretPoint,
284 const WSRunScanner& aWSRunScannerAtCaret,
285 AutoRangeArray& aRangesToDelete) const;
288 * HandleDeleteAtOtherBlockBoundary() handles deletion at other block boundary
289 * (i.e., immediately before or after a block). If this does not join blocks,
290 * `Run()` may be called recursively with creating another instance.
292 * @param aDirectionAndAmount Direction of the deletion.
293 * @param aStripWrappers Must be eStrip or eNoStrip.
294 * @param aOtherBlockElement The block element which follows the caret or
295 * is followed by caret.
296 * @param aCaretPoint The caret point (i.e., selection start or
297 * end).
298 * @param aWSRunScannerAtCaret WSRunScanner instance which was initialized
299 * with the caret point.
300 * @param aRangesToDelete Ranges to delete of the caller. This should
301 * be collapsed and the point should match with
302 * aCaretPoint.
304 [[nodiscard]] MOZ_CAN_RUN_SCRIPT EditActionResult
305 HandleDeleteAtOtherBlockBoundary(HTMLEditor& aHTMLEditor,
306 nsIEditor::EDirection aDirectionAndAmount,
307 nsIEditor::EStripWrappers aStripWrappers,
308 Element& aOtherBlockElement,
309 const EditorDOMPoint& aCaretPoint,
310 WSRunScanner& aWSRunScannerAtCaret,
311 AutoRangeArray& aRangesToDelete);
314 * ExtendRangeToIncludeInvisibleNodes() extends aRange if there are some
315 * invisible nodes around it.
317 * @param aFrameSelection If the caller wants range in selection limiter,
318 * set this to non-nullptr which knows the limiter.
319 * @param aRange The range to be extended. This must not be
320 * collapsed, must be positioned, and must not be
321 * in selection.
322 * @return true if succeeded to set the range.
324 bool ExtendRangeToIncludeInvisibleNodes(
325 const HTMLEditor& aHTMLEditor, const nsFrameSelection* aFrameSelection,
326 nsRange& aRange) const;
329 * ShouldDeleteHRElement() checks whether aHRElement should be deleted
330 * when selection is collapsed at aCaretPoint.
332 Result<bool, nsresult> ShouldDeleteHRElement(
333 const HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount,
334 Element& aHRElement, const EditorDOMPoint& aCaretPoint) const;
337 * DeleteUnnecessaryNodesAndCollapseSelection() removes unnecessary nodes
338 * around aSelectionStartPoint and aSelectionEndPoint. Then, collapse
339 * selection at aSelectionStartPoint or aSelectionEndPoint (depending on
340 * aDirectionAndAmount).
342 * @param aDirectionAndAmount Direction of the deletion.
343 * If nsIEditor::ePrevious, selection
344 * will be collapsed to aSelectionEndPoint. Otherwise, selection will be
345 * collapsed to aSelectionStartPoint.
346 * @param aSelectionStartPoint First selection range start after
347 * computing the deleting range.
348 * @param aSelectionEndPoint First selection range end after
349 * computing the deleting range.
351 [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult
352 DeleteUnnecessaryNodesAndCollapseSelection(
353 HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount,
354 const EditorDOMPoint& aSelectionStartPoint,
355 const EditorDOMPoint& aSelectionEndPoint);
358 * If aContent is a text node that contains only collapsed white-space or
359 * empty and editable.
361 [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult
362 DeleteNodeIfInvisibleAndEditableTextNode(HTMLEditor& aHTMLEditor,
363 nsIContent& aContent);
366 * DeleteParentBlocksIfEmpty() removes parent block elements if they
367 * don't have visible contents. Note that due performance issue of
368 * WhiteSpaceVisibilityKeeper, this call may be expensive. And also note that
369 * this removes a empty block with a transaction. So, please make sure that
370 * you've already created `AutoPlaceholderBatch`.
372 * @param aPoint The point whether this method climbing up the DOM
373 * tree to remove empty parent blocks.
374 * @return NS_OK if one or more empty block parents are deleted.
375 * NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND if the point is
376 * not in empty block.
377 * Or NS_ERROR_* if something unexpected occurs.
379 [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult
380 DeleteParentBlocksWithTransactionIfEmpty(HTMLEditor& aHTMLEditor,
381 const EditorDOMPoint& aPoint);
383 [[nodiscard]] MOZ_CAN_RUN_SCRIPT EditActionResult
384 FallbackToDeleteRangesWithTransaction(HTMLEditor& aHTMLEditor,
385 AutoRangeArray& aRangesToDelete) {
386 MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable());
387 MOZ_ASSERT(CanFallbackToDeleteRangesWithTransaction(aRangesToDelete));
388 nsresult rv = aHTMLEditor.DeleteRangesWithTransaction(
389 mOriginalDirectionAndAmount, mOriginalStripWrappers, aRangesToDelete);
390 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
391 "HTMLEditor::DeleteRangesWithTransaction() failed");
392 return EditActionHandled(rv); // Don't return "ignored" for avoiding to
393 // fall it back again.
397 * ComputeRangesToDeleteRangesWithTransaction() computes target ranges
398 * which will be called by `EditorBase::DeleteRangesWithTransaction()`.
399 * TODO: We should not use it for consistency with each deletion handler
400 * in this and nested classes.
402 nsresult ComputeRangesToDeleteRangesWithTransaction(
403 const HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount,
404 AutoRangeArray& aRangesToDelete) const;
406 nsresult FallbackToComputeRangesToDeleteRangesWithTransaction(
407 const HTMLEditor& aHTMLEditor, AutoRangeArray& aRangesToDelete) const {
408 MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable());
409 MOZ_ASSERT(CanFallbackToDeleteRangesWithTransaction(aRangesToDelete));
410 nsresult rv = ComputeRangesToDeleteRangesWithTransaction(
411 aHTMLEditor, mOriginalDirectionAndAmount, aRangesToDelete);
412 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
413 "AutoDeleteRangesHandler::"
414 "ComputeRangesToDeleteRangesWithTransaction() failed");
415 return rv;
418 class MOZ_STACK_CLASS AutoBlockElementsJoiner final {
419 public:
420 AutoBlockElementsJoiner() = delete;
421 explicit AutoBlockElementsJoiner(
422 AutoDeleteRangesHandler& aDeleteRangesHandler)
423 : mDeleteRangesHandler(&aDeleteRangesHandler),
424 mDeleteRangesHandlerConst(aDeleteRangesHandler) {}
425 explicit AutoBlockElementsJoiner(
426 const AutoDeleteRangesHandler& aDeleteRangesHandler)
427 : mDeleteRangesHandler(nullptr),
428 mDeleteRangesHandlerConst(aDeleteRangesHandler) {}
431 * PrepareToDeleteAtCurrentBlockBoundary() considers left content and right
432 * content which are joined for handling deletion at current block boundary
433 * (i.e., at start or end of the current block).
435 * @param aHTMLEditor The HTML editor.
436 * @param aDirectionAndAmount Direction of the deletion.
437 * @param aCurrentBlockElement The current block element.
438 * @param aCaretPoint The caret point (i.e., selection start
439 * or end).
440 * @return true if can continue to handle the
441 * deletion.
443 bool PrepareToDeleteAtCurrentBlockBoundary(
444 const HTMLEditor& aHTMLEditor,
445 nsIEditor::EDirection aDirectionAndAmount,
446 Element& aCurrentBlockElement, const EditorDOMPoint& aCaretPoint);
449 * PrepareToDeleteAtOtherBlockBoundary() considers left content and right
450 * content which are joined for handling deletion at other block boundary
451 * (i.e., immediately before or after a block).
453 * @param aHTMLEditor The HTML editor.
454 * @param aDirectionAndAmount Direction of the deletion.
455 * @param aOtherBlockElement The block element which follows the
456 * caret or is followed by caret.
457 * @param aCaretPoint The caret point (i.e., selection start
458 * or end).
459 * @param aWSRunScannerAtCaret WSRunScanner instance which was
460 * initialized with the caret point.
461 * @return true if can continue to handle the
462 * deletion.
464 bool PrepareToDeleteAtOtherBlockBoundary(
465 const HTMLEditor& aHTMLEditor,
466 nsIEditor::EDirection aDirectionAndAmount, Element& aOtherBlockElement,
467 const EditorDOMPoint& aCaretPoint,
468 const WSRunScanner& aWSRunScannerAtCaret);
471 * PrepareToDeleteNonCollapsedRanges() considers left block element and
472 * right block element which are inclusive ancestor block element of
473 * start and end container of first range of aRangesToDelete.
475 * @param aHTMLEditor The HTML editor.
476 * @param aRangesToDelete Ranges to delete. Must not be
477 * collapsed.
478 * @return true if can continue to handle the
479 * deletion.
481 bool PrepareToDeleteNonCollapsedRanges(
482 const HTMLEditor& aHTMLEditor, const AutoRangeArray& aRangesToDelete);
485 * Run() executes the joining.
487 * @param aHTMLEditor The HTML editor.
488 * @param aDirectionAndAmount Direction of the deletion.
489 * @param aStripWrappers Must be eStrip or eNoStrip.
490 * @param aCaretPoint The caret point (i.e., selection start
491 * or end).
492 * @param aRangesToDelete Ranges to delete of the caller.
493 * This should be collapsed and match
494 * with aCaretPoint.
496 [[nodiscard]] MOZ_CAN_RUN_SCRIPT EditActionResult
497 Run(HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount,
498 nsIEditor::EStripWrappers aStripWrappers,
499 const EditorDOMPoint& aCaretPoint, AutoRangeArray& aRangesToDelete) {
500 switch (mMode) {
501 case Mode::JoinCurrentBlock: {
502 EditActionResult result =
503 HandleDeleteAtCurrentBlockBoundary(aHTMLEditor, aCaretPoint);
504 NS_WARNING_ASSERTION(result.Succeeded(),
505 "AutoBlockElementsJoiner::"
506 "HandleDeleteAtCurrentBlockBoundary() failed");
507 return result;
509 case Mode::JoinOtherBlock: {
510 EditActionResult result = HandleDeleteAtOtherBlockBoundary(
511 aHTMLEditor, aDirectionAndAmount, aStripWrappers, aCaretPoint,
512 aRangesToDelete);
513 NS_WARNING_ASSERTION(result.Succeeded(),
514 "AutoBlockElementsJoiner::"
515 "HandleDeleteAtOtherBlockBoundary() failed");
516 return result;
518 case Mode::DeleteBRElement: {
519 EditActionResult result =
520 DeleteBRElement(aHTMLEditor, aDirectionAndAmount, aCaretPoint);
521 NS_WARNING_ASSERTION(
522 result.Succeeded(),
523 "AutoBlockElementsJoiner::DeleteBRElement() failed");
524 return result;
526 case Mode::JoinBlocksInSameParent:
527 case Mode::DeleteContentInRanges:
528 case Mode::DeleteNonCollapsedRanges:
529 MOZ_ASSERT_UNREACHABLE(
530 "This mode should be handled in the other Run()");
531 return EditActionResult(NS_ERROR_UNEXPECTED);
532 case Mode::NotInitialized:
533 return EditActionIgnored();
535 return EditActionResult(NS_ERROR_NOT_INITIALIZED);
538 nsresult ComputeRangesToDelete(const HTMLEditor& aHTMLEditor,
539 nsIEditor::EDirection aDirectionAndAmount,
540 const EditorDOMPoint& aCaretPoint,
541 AutoRangeArray& aRangesToDelete) const {
542 switch (mMode) {
543 case Mode::JoinCurrentBlock: {
544 nsresult rv = ComputeRangesToDeleteAtCurrentBlockBoundary(
545 aHTMLEditor, aCaretPoint, aRangesToDelete);
546 NS_WARNING_ASSERTION(
547 NS_SUCCEEDED(rv),
548 "AutoBlockElementsJoiner::"
549 "ComputeRangesToDeleteAtCurrentBlockBoundary() failed");
550 return rv;
552 case Mode::JoinOtherBlock: {
553 nsresult rv = ComputeRangesToDeleteAtOtherBlockBoundary(
554 aHTMLEditor, aDirectionAndAmount, aCaretPoint, aRangesToDelete);
555 NS_WARNING_ASSERTION(
556 NS_SUCCEEDED(rv),
557 "AutoBlockElementsJoiner::"
558 "ComputeRangesToDeleteAtOtherBlockBoundary() failed");
559 return rv;
561 case Mode::DeleteBRElement: {
562 nsresult rv = ComputeRangesToDeleteBRElement(aRangesToDelete);
563 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
564 "AutoBlockElementsJoiner::"
565 "ComputeRangesToDeleteBRElement() failed");
566 return rv;
568 case Mode::JoinBlocksInSameParent:
569 case Mode::DeleteContentInRanges:
570 case Mode::DeleteNonCollapsedRanges:
571 MOZ_ASSERT_UNREACHABLE(
572 "This mode should be handled in the other "
573 "ComputeRangesToDelete()");
574 return NS_ERROR_UNEXPECTED;
575 case Mode::NotInitialized:
576 return NS_OK;
578 return NS_ERROR_NOT_IMPLEMENTED;
582 * Run() executes the joining.
584 * @param aHTMLEditor The HTML editor.
585 * @param aDirectionAndAmount Direction of the deletion.
586 * @param aStripWrappers Whether delete or keep new empty
587 * ancestor elements.
588 * @param aRangesToDelete Ranges to delete. Must not be
589 * collapsed.
590 * @param aSelectionWasCollapsed Whether selection was or was not
591 * collapsed when starting to handle
592 * deletion.
594 [[nodiscard]] MOZ_CAN_RUN_SCRIPT EditActionResult
595 Run(HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount,
596 nsIEditor::EStripWrappers aStripWrappers,
597 AutoRangeArray& aRangesToDelete,
598 AutoDeleteRangesHandler::SelectionWasCollapsed aSelectionWasCollapsed) {
599 switch (mMode) {
600 case Mode::JoinCurrentBlock:
601 case Mode::JoinOtherBlock:
602 case Mode::DeleteBRElement:
603 MOZ_ASSERT_UNREACHABLE(
604 "This mode should be handled in the other Run()");
605 return EditActionResult(NS_ERROR_UNEXPECTED);
606 case Mode::JoinBlocksInSameParent: {
607 EditActionResult result =
608 JoinBlockElementsInSameParent(aHTMLEditor, aDirectionAndAmount,
609 aStripWrappers, aRangesToDelete);
610 NS_WARNING_ASSERTION(result.Succeeded(),
611 "AutoBlockElementsJoiner::"
612 "JoinBlockElementsInSameParent() failed");
613 return result;
615 case Mode::DeleteContentInRanges: {
616 EditActionResult result =
617 DeleteContentInRanges(aHTMLEditor, aDirectionAndAmount,
618 aStripWrappers, aRangesToDelete);
619 NS_WARNING_ASSERTION(
620 result.Succeeded(),
621 "AutoBlockElementsJoiner::DeleteContentInRanges() failed");
622 return result;
624 case Mode::DeleteNonCollapsedRanges: {
625 EditActionResult result = HandleDeleteNonCollapsedRanges(
626 aHTMLEditor, aDirectionAndAmount, aStripWrappers, aRangesToDelete,
627 aSelectionWasCollapsed);
628 NS_WARNING_ASSERTION(result.Succeeded(),
629 "AutoBlockElementsJoiner::"
630 "HandleDeleteNonCollapsedRange() failed");
631 return result;
633 case Mode::NotInitialized:
634 MOZ_ASSERT_UNREACHABLE(
635 "Call Run() after calling a preparation method");
636 return EditActionIgnored();
638 return EditActionResult(NS_ERROR_NOT_INITIALIZED);
641 nsresult ComputeRangesToDelete(
642 const HTMLEditor& aHTMLEditor,
643 nsIEditor::EDirection aDirectionAndAmount,
644 AutoRangeArray& aRangesToDelete,
645 AutoDeleteRangesHandler::SelectionWasCollapsed aSelectionWasCollapsed)
646 const {
647 switch (mMode) {
648 case Mode::JoinCurrentBlock:
649 case Mode::JoinOtherBlock:
650 case Mode::DeleteBRElement:
651 MOZ_ASSERT_UNREACHABLE(
652 "This mode should be handled in the other "
653 "ComputeRangesToDelete()");
654 return NS_ERROR_UNEXPECTED;
655 case Mode::JoinBlocksInSameParent: {
656 nsresult rv = ComputeRangesToJoinBlockElementsInSameParent(
657 aHTMLEditor, aDirectionAndAmount, aRangesToDelete);
658 NS_WARNING_ASSERTION(
659 NS_SUCCEEDED(rv),
660 "AutoBlockElementsJoiner::"
661 "ComputeRangesToJoinBlockElementsInSameParent() failed");
662 return rv;
664 case Mode::DeleteContentInRanges: {
665 nsresult rv = ComputeRangesToDeleteContentInRanges(
666 aHTMLEditor, aDirectionAndAmount, aRangesToDelete);
667 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
668 "AutoBlockElementsJoiner::"
669 "ComputeRangesToDeleteContentInRanges() failed");
670 return rv;
672 case Mode::DeleteNonCollapsedRanges: {
673 nsresult rv = ComputeRangesToDeleteNonCollapsedRanges(
674 aHTMLEditor, aDirectionAndAmount, aRangesToDelete,
675 aSelectionWasCollapsed);
676 NS_WARNING_ASSERTION(
677 NS_SUCCEEDED(rv),
678 "AutoBlockElementsJoiner::"
679 "ComputeRangesToDeleteNonCollapsedRanges() failed");
680 return rv;
682 case Mode::NotInitialized:
683 MOZ_ASSERT_UNREACHABLE(
684 "Call ComputeRangesToDelete() after calling a preparation "
685 "method");
686 return NS_ERROR_NOT_INITIALIZED;
688 return NS_ERROR_NOT_INITIALIZED;
691 nsIContent* GetLeafContentInOtherBlockElement() const {
692 MOZ_ASSERT(mMode == Mode::JoinOtherBlock);
693 return mLeafContentInOtherBlock;
696 private:
697 [[nodiscard]] MOZ_CAN_RUN_SCRIPT EditActionResult
698 HandleDeleteAtCurrentBlockBoundary(HTMLEditor& aHTMLEditor,
699 const EditorDOMPoint& aCaretPoint);
700 nsresult ComputeRangesToDeleteAtCurrentBlockBoundary(
701 const HTMLEditor& aHTMLEditor, const EditorDOMPoint& aCaretPoint,
702 AutoRangeArray& aRangesToDelete) const;
703 [[nodiscard]] MOZ_CAN_RUN_SCRIPT EditActionResult
704 HandleDeleteAtOtherBlockBoundary(HTMLEditor& aHTMLEditor,
705 nsIEditor::EDirection aDirectionAndAmount,
706 nsIEditor::EStripWrappers aStripWrappers,
707 const EditorDOMPoint& aCaretPoint,
708 AutoRangeArray& aRangesToDelete);
709 // FYI: This method may modify selection, but it won't cause running
710 // script because of `AutoHideSelectionChanges` which blocks
711 // selection change listeners and the selection change event
712 // dispatcher.
713 MOZ_CAN_RUN_SCRIPT_BOUNDARY nsresult
714 ComputeRangesToDeleteAtOtherBlockBoundary(
715 const HTMLEditor& aHTMLEditor,
716 nsIEditor::EDirection aDirectionAndAmount,
717 const EditorDOMPoint& aCaretPoint,
718 AutoRangeArray& aRangesToDelete) const;
719 [[nodiscard]] MOZ_CAN_RUN_SCRIPT EditActionResult
720 JoinBlockElementsInSameParent(HTMLEditor& aHTMLEditor,
721 nsIEditor::EDirection aDirectionAndAmount,
722 nsIEditor::EStripWrappers aStripWrappers,
723 AutoRangeArray& aRangesToDelete);
724 nsresult ComputeRangesToJoinBlockElementsInSameParent(
725 const HTMLEditor& aHTMLEditor,
726 nsIEditor::EDirection aDirectionAndAmount,
727 AutoRangeArray& aRangesToDelete) const;
728 [[nodiscard]] MOZ_CAN_RUN_SCRIPT EditActionResult DeleteBRElement(
729 HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount,
730 const EditorDOMPoint& aCaretPoint);
731 nsresult ComputeRangesToDeleteBRElement(
732 AutoRangeArray& aRangesToDelete) const;
733 [[nodiscard]] MOZ_CAN_RUN_SCRIPT EditActionResult DeleteContentInRanges(
734 HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount,
735 nsIEditor::EStripWrappers aStripWrappers,
736 AutoRangeArray& aRangesToDelete);
737 nsresult ComputeRangesToDeleteContentInRanges(
738 const HTMLEditor& aHTMLEditor,
739 nsIEditor::EDirection aDirectionAndAmount,
740 AutoRangeArray& aRangesToDelete) const;
741 [[nodiscard]] MOZ_CAN_RUN_SCRIPT EditActionResult
742 HandleDeleteNonCollapsedRanges(
743 HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount,
744 nsIEditor::EStripWrappers aStripWrappers,
745 AutoRangeArray& aRangesToDelete,
746 AutoDeleteRangesHandler::SelectionWasCollapsed aSelectionWasCollapsed);
747 nsresult ComputeRangesToDeleteNonCollapsedRanges(
748 const HTMLEditor& aHTMLEditor,
749 nsIEditor::EDirection aDirectionAndAmount,
750 AutoRangeArray& aRangesToDelete,
751 AutoDeleteRangesHandler::SelectionWasCollapsed aSelectionWasCollapsed)
752 const;
755 * JoinNodesDeepWithTransaction() joins aLeftNode and aRightNode "deeply".
756 * First, they are joined simply, then, new right node is assumed as the
757 * child at length of the left node before joined and new left node is
758 * assumed as its previous sibling. Then, they will be joined again.
759 * And then, these steps are repeated.
761 * @param aLeftContent The node which will be removed form the tree.
762 * @param aRightContent The node which will be inserted the contents of
763 * aRightContent.
764 * @return The point of the first child of the last right
765 * node. The result is always set if this succeeded.
767 MOZ_CAN_RUN_SCRIPT Result<EditorDOMPoint, nsresult>
768 JoinNodesDeepWithTransaction(HTMLEditor& aHTMLEditor,
769 nsIContent& aLeftContent,
770 nsIContent& aRightContent);
773 * DeleteNodesEntirelyInRangeButKeepTableStructure() removes nodes which are
774 * entirely in aRange. Howevers, if some nodes are part of a table,
775 * removes all children of them instead. I.e., this does not make damage to
776 * table structure at the range, but may remove table entirely if it's
777 * in the range.
779 * @return true if inclusive ancestor block elements at
780 * start and end of the range should be joined.
782 MOZ_CAN_RUN_SCRIPT Result<bool, nsresult>
783 DeleteNodesEntirelyInRangeButKeepTableStructure(
784 HTMLEditor& aHTMLEditor, nsRange& aRange,
785 AutoDeleteRangesHandler::SelectionWasCollapsed aSelectionWasCollapsed);
786 bool NeedsToJoinNodesAfterDeleteNodesEntirelyInRangeButKeepTableStructure(
787 const HTMLEditor& aHTMLEditor,
788 const nsTArray<OwningNonNull<nsIContent>>& aArrayOfContents,
789 AutoDeleteRangesHandler::SelectionWasCollapsed aSelectionWasCollapsed)
790 const;
791 Result<bool, nsresult>
792 ComputeRangesToDeleteNodesEntirelyInRangeButKeepTableStructure(
793 const HTMLEditor& aHTMLEditor, nsRange& aRange,
794 AutoDeleteRangesHandler::SelectionWasCollapsed aSelectionWasCollapsed)
795 const;
798 * DeleteContentButKeepTableStructure() removes aContent if it's an element
799 * which is part of a table structure. If it's a part of table structure,
800 * removes its all children recursively. I.e., this may delete all of a
801 * table, but won't break table structure partially.
803 * @param aContent The content which or whose all children should
804 * be removed.
806 [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult
807 DeleteContentButKeepTableStructure(HTMLEditor& aHTMLEditor,
808 nsIContent& aContent);
811 * DeleteTextAtStartAndEndOfRange() removes text if start and/or end of
812 * aRange is in a text node.
814 [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult
815 DeleteTextAtStartAndEndOfRange(HTMLEditor& aHTMLEditor, nsRange& aRange);
817 class MOZ_STACK_CLASS AutoInclusiveAncestorBlockElementsJoiner final {
818 public:
819 AutoInclusiveAncestorBlockElementsJoiner() = delete;
820 AutoInclusiveAncestorBlockElementsJoiner(
821 nsIContent& aInclusiveDescendantOfLeftBlockElement,
822 nsIContent& aInclusiveDescendantOfRightBlockElement)
823 : mInclusiveDescendantOfLeftBlockElement(
824 aInclusiveDescendantOfLeftBlockElement),
825 mInclusiveDescendantOfRightBlockElement(
826 aInclusiveDescendantOfRightBlockElement),
827 mCanJoinBlocks(false),
828 mFallbackToDeleteLeafContent(false) {}
830 bool IsSet() const { return mLeftBlockElement && mRightBlockElement; }
831 bool IsSameBlockElement() const {
832 return mLeftBlockElement && mLeftBlockElement == mRightBlockElement;
836 * Prepare for joining inclusive ancestor block elements. When this
837 * returns false, the deletion should be canceled.
839 Result<bool, nsresult> Prepare(const HTMLEditor& aHTMLEditor);
842 * When this returns true, this can join the blocks with `Run()`.
844 bool CanJoinBlocks() const { return mCanJoinBlocks; }
847 * When this returns true, `Run()` must return "ignored" so that
848 * caller can skip calling `Run()`. This is available only when
849 * `CanJoinBlocks()` returns `true`.
850 * TODO: This should be merged into `CanJoinBlocks()` in the future.
852 bool ShouldDeleteLeafContentInstead() const {
853 MOZ_ASSERT(CanJoinBlocks());
854 return mFallbackToDeleteLeafContent;
858 * ComputeRangesToDelete() extends aRangesToDelete includes the element
859 * boundaries between joining blocks. If they won't be joined, this
860 * collapses the range to aCaretPoint.
862 nsresult ComputeRangesToDelete(const HTMLEditor& aHTMLEditor,
863 const EditorDOMPoint& aCaretPoint,
864 AutoRangeArray& aRangesToDelete) const;
867 * Join inclusive ancestor block elements which are found by preceding
868 * Preare() call.
869 * The right element is always joined to the left element.
870 * If the elements are the same type and not nested within each other,
871 * JoinEditableNodesWithTransaction() is called (example, joining two
872 * list items together into one).
873 * If the elements are not the same type, or one is a descendant of the
874 * other, we instead destroy the right block placing its children into
875 * left block.
877 [[nodiscard]] MOZ_CAN_RUN_SCRIPT EditActionResult
878 Run(HTMLEditor& aHTMLEditor);
880 private:
882 * This method returns true when
883 * `MergeFirstLineOfRightBlockElementIntoDescendantLeftBlockElement()`,
884 * `MergeFirstLineOfRightBlockElementIntoAncestorLeftBlockElement()` and
885 * `MergeFirstLineOfRightBlockElementIntoLeftBlockElement()` handle it
886 * with the `if` block of their main blocks.
888 bool CanMergeLeftAndRightBlockElements() const {
889 if (!IsSet()) {
890 return false;
892 // `MergeFirstLineOfRightBlockElementIntoDescendantLeftBlockElement()`
893 if (mPointContainingTheOtherBlockElement.GetContainer() ==
894 mRightBlockElement) {
895 return mNewListElementTagNameOfRightListElement.isSome();
897 // `MergeFirstLineOfRightBlockElementIntoAncestorLeftBlockElement()`
898 if (mPointContainingTheOtherBlockElement.GetContainer() ==
899 mLeftBlockElement) {
900 return mNewListElementTagNameOfRightListElement.isSome() &&
901 !mRightBlockElement->GetChildCount();
903 MOZ_ASSERT(!mPointContainingTheOtherBlockElement.IsSet());
904 // `MergeFirstLineOfRightBlockElementIntoLeftBlockElement()`
905 return mNewListElementTagNameOfRightListElement.isSome() ||
906 mLeftBlockElement->NodeInfo()->NameAtom() ==
907 mRightBlockElement->NodeInfo()->NameAtom();
910 OwningNonNull<nsIContent> mInclusiveDescendantOfLeftBlockElement;
911 OwningNonNull<nsIContent> mInclusiveDescendantOfRightBlockElement;
912 RefPtr<Element> mLeftBlockElement;
913 RefPtr<Element> mRightBlockElement;
914 Maybe<nsAtom*> mNewListElementTagNameOfRightListElement;
915 EditorDOMPoint mPointContainingTheOtherBlockElement;
916 RefPtr<dom::HTMLBRElement> mPrecedingInvisibleBRElement;
917 bool mCanJoinBlocks;
918 bool mFallbackToDeleteLeafContent;
919 }; // HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
920 // AutoInclusiveAncestorBlockElementsJoiner
922 enum class Mode {
923 NotInitialized,
924 JoinCurrentBlock,
925 JoinOtherBlock,
926 JoinBlocksInSameParent,
927 DeleteBRElement,
928 DeleteContentInRanges,
929 DeleteNonCollapsedRanges,
931 AutoDeleteRangesHandler* mDeleteRangesHandler;
932 const AutoDeleteRangesHandler& mDeleteRangesHandlerConst;
933 nsCOMPtr<nsIContent> mLeftContent;
934 nsCOMPtr<nsIContent> mRightContent;
935 nsCOMPtr<nsIContent> mLeafContentInOtherBlock;
936 RefPtr<dom::HTMLBRElement> mBRElement;
937 Mode mMode = Mode::NotInitialized;
938 }; // HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner
940 class MOZ_STACK_CLASS AutoEmptyBlockAncestorDeleter final {
941 public:
943 * ScanEmptyBlockInclusiveAncestor() scans an inclusive ancestor element
944 * which is empty and a block element. Then, stores the result and
945 * returns the found empty block element.
947 * @param aHTMLEditor The HTMLEditor.
948 * @param aStartContent Start content to look for empty ancestors.
949 * @param aEditingHostElement Current editing host.
951 [[nodiscard]] Element* ScanEmptyBlockInclusiveAncestor(
952 const HTMLEditor& aHTMLEditor, nsIContent& aStartContent,
953 Element& aEditingHostElement);
956 * ComputeTargetRanges() computes "target ranges" for deleting
957 * `mEmptyInclusiveAncestorBlockElement`.
959 nsresult ComputeTargetRanges(const HTMLEditor& aHTMLEditor,
960 nsIEditor::EDirection aDirectionAndAmount,
961 const Element& aEditingHost,
962 AutoRangeArray& aRangesToDelete) const;
965 * Deletes found empty block element by `ScanEmptyBlockInclusiveAncestor()`.
966 * If found one is a list item element, calls
967 * `MaybeInsertBRElementBeforeEmptyListItemElement()` before deleting
968 * the list item element.
969 * If found empty ancestor is not a list item element,
970 * `GetNewCaretPoisition()` will be called to determine new caret position.
971 * Finally, removes the empty block ancestor.
973 * @param aHTMLEditor The HTMLEditor.
974 * @param aDirectionAndAmount If found empty ancestor block is a list item
975 * element, this is ignored. Otherwise:
976 * - If eNext, eNextWord or eToEndOfLine,
977 * collapse Selection to after found empty
978 * ancestor.
979 * - If ePrevious, ePreviousWord or
980 * eToBeginningOfLine, collapse Selection to
981 * end of previous editable node.
982 * - Otherwise, eNone is allowed but does
983 * nothing.
985 [[nodiscard]] MOZ_CAN_RUN_SCRIPT EditActionResult
986 Run(HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount);
988 private:
990 * MaybeInsertBRElementBeforeEmptyListItemElement() inserts a `<br>` element
991 * if `mEmptyInclusiveAncestorBlockElement` is a list item element which
992 * is first editable element in its parent, and its grand parent is not a
993 * list element, inserts a `<br>` element before the empty list item.
995 [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<RefPtr<Element>, nsresult>
996 MaybeInsertBRElementBeforeEmptyListItemElement(HTMLEditor& aHTMLEditor);
999 * GetNewCaretPoisition() returns new caret position after deleting
1000 * `mEmptyInclusiveAncestorBlockElement`.
1002 [[nodiscard]] Result<EditorDOMPoint, nsresult> GetNewCaretPoisition(
1003 const HTMLEditor& aHTMLEditor,
1004 nsIEditor::EDirection aDirectionAndAmount) const;
1006 RefPtr<Element> mEmptyInclusiveAncestorBlockElement;
1007 }; // HTMLEditor::AutoDeleteRangesHandler::AutoEmptyBlockAncestorDeleter
1009 const AutoDeleteRangesHandler* const mParent;
1010 nsIEditor::EDirection mOriginalDirectionAndAmount;
1011 nsIEditor::EStripWrappers mOriginalStripWrappers;
1012 }; // HTMLEditor::AutoDeleteRangesHandler
1014 nsresult HTMLEditor::ComputeTargetRanges(
1015 nsIEditor::EDirection aDirectionAndAmount,
1016 AutoRangeArray& aRangesToDelete) {
1017 MOZ_ASSERT(IsEditActionDataAvailable());
1019 // First check for table selection mode. If so, hand off to table editor.
1020 SelectedTableCellScanner scanner(aRangesToDelete);
1021 if (scanner.IsInTableCellSelectionMode()) {
1022 // If it's in table cell selection mode, we'll delete all childen in
1023 // the all selected table cell elements,
1024 if (scanner.ElementsRef().Length() == aRangesToDelete.Ranges().Length()) {
1025 return NS_OK;
1027 // but will ignore all ranges which does not select a table cell.
1028 size_t removedRanges = 0;
1029 for (size_t i = 1; i < scanner.ElementsRef().Length(); i++) {
1030 if (HTMLEditUtils::GetTableCellElementIfOnlyOneSelected(
1031 aRangesToDelete.Ranges()[i - removedRanges]) !=
1032 scanner.ElementsRef()[i]) {
1033 aRangesToDelete.Ranges().RemoveElementAt(i - removedRanges);
1034 removedRanges++;
1037 return NS_OK;
1040 AutoDeleteRangesHandler deleteHandler;
1041 nsresult rv = deleteHandler.ComputeRangesToDelete(*this, aDirectionAndAmount,
1042 aRangesToDelete);
1043 NS_WARNING_ASSERTION(
1044 NS_SUCCEEDED(rv),
1045 "AutoDeleteRangesHandler::ComputeRangesToDelete() failed");
1046 return rv;
1049 EditActionResult HTMLEditor::HandleDeleteSelection(
1050 nsIEditor::EDirection aDirectionAndAmount,
1051 nsIEditor::EStripWrappers aStripWrappers) {
1052 MOZ_ASSERT(IsEditActionDataAvailable());
1053 MOZ_ASSERT(aStripWrappers == nsIEditor::eStrip ||
1054 aStripWrappers == nsIEditor::eNoStrip);
1056 if (!SelectionRefPtr()->RangeCount()) {
1057 return EditActionCanceled();
1060 // Remember that we did a selection deletion. Used by
1061 // CreateStyleForInsertText()
1062 TopLevelEditSubActionDataRef().mDidDeleteSelection = true;
1064 // If there is only padding `<br>` element for empty editor, cancel the
1065 // operation.
1066 if (mPaddingBRElementForEmptyEditor) {
1067 return EditActionCanceled();
1070 // First check for table selection mode. If so, hand off to table editor.
1071 if (HTMLEditUtils::IsInTableCellSelectionMode(*SelectionRefPtr())) {
1072 nsresult rv = DeleteTableCellContentsWithTransaction();
1073 if (NS_WARN_IF(Destroyed())) {
1074 return EditActionResult(NS_ERROR_EDITOR_DESTROYED);
1076 NS_WARNING_ASSERTION(
1077 NS_SUCCEEDED(rv),
1078 "HTMLEditor::DeleteTableCellContentsWithTransaction() failed");
1079 return EditActionHandled(rv);
1082 AutoRangeArray rangesToDelete(*SelectionRefPtr());
1083 AutoDeleteRangesHandler deleteHandler;
1084 EditActionResult result = deleteHandler.Run(*this, aDirectionAndAmount,
1085 aStripWrappers, rangesToDelete);
1086 if (result.Failed() || result.Canceled()) {
1087 NS_WARNING_ASSERTION(result.Succeeded(),
1088 "AutoDeleteRangesHandler::Run() failed");
1089 return result;
1092 // XXX At here, selection may have no range because of mutation event
1093 // listeners can do anything so that we should just return NS_OK instead
1094 // of returning error.
1095 EditorDOMPoint atNewStartOfSelection(
1096 EditorBase::GetStartPoint(*SelectionRefPtr()));
1097 if (NS_WARN_IF(!atNewStartOfSelection.IsSet())) {
1098 return EditActionHandled(NS_ERROR_FAILURE);
1100 if (atNewStartOfSelection.GetContainerAsContent()) {
1101 nsresult rv = DeleteMostAncestorMailCiteElementIfEmpty(
1102 MOZ_KnownLive(*atNewStartOfSelection.GetContainerAsContent()));
1103 if (NS_FAILED(rv)) {
1104 NS_WARNING(
1105 "HTMLEditor::DeleteMostAncestorMailCiteElementIfEmpty() failed");
1106 return EditActionHandled(rv);
1109 return EditActionHandled();
1112 nsresult HTMLEditor::AutoDeleteRangesHandler::ComputeRangesToDelete(
1113 const HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount,
1114 AutoRangeArray& aRangesToDelete) {
1115 MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable());
1116 MOZ_ASSERT(!aRangesToDelete.Ranges().IsEmpty());
1118 mOriginalDirectionAndAmount = aDirectionAndAmount;
1119 mOriginalStripWrappers = nsIEditor::eNoStrip;
1121 if (aHTMLEditor.mPaddingBRElementForEmptyEditor) {
1122 nsresult rv = aRangesToDelete.Collapse(
1123 EditorRawDOMPoint(aHTMLEditor.mPaddingBRElementForEmptyEditor));
1124 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "AutoRangeArray::Collapse() failed");
1125 return rv;
1128 SelectionWasCollapsed selectionWasCollapsed = aRangesToDelete.IsCollapsed()
1129 ? SelectionWasCollapsed::Yes
1130 : SelectionWasCollapsed::No;
1131 if (selectionWasCollapsed == SelectionWasCollapsed::Yes) {
1132 EditorDOMPoint startPoint(aRangesToDelete.GetStartPointOfFirstRange());
1133 if (NS_WARN_IF(!startPoint.IsSet())) {
1134 return NS_ERROR_FAILURE;
1136 RefPtr<Element> editingHost = aHTMLEditor.GetActiveEditingHost();
1137 if (NS_WARN_IF(!editingHost)) {
1138 return NS_ERROR_FAILURE;
1140 if (startPoint.GetContainerAsContent()) {
1141 AutoEmptyBlockAncestorDeleter deleter;
1142 if (deleter.ScanEmptyBlockInclusiveAncestor(
1143 aHTMLEditor, *startPoint.GetContainerAsContent(), *editingHost)) {
1144 nsresult rv = deleter.ComputeTargetRanges(
1145 aHTMLEditor, aDirectionAndAmount, *editingHost, aRangesToDelete);
1146 NS_WARNING_ASSERTION(
1147 NS_SUCCEEDED(rv),
1148 "AutoEmptyBlockAncestorDeleter::ComputeTargetRanges() failed");
1149 return rv;
1153 // We shouldn't update caret bidi level right now, but we need to check
1154 // whether the deletion will be canceled or not.
1155 AutoCaretBidiLevelManager bidiLevelManager(aHTMLEditor, aDirectionAndAmount,
1156 startPoint);
1157 if (bidiLevelManager.Failed()) {
1158 NS_WARNING(
1159 "EditorBase::AutoCaretBidiLevelManager failed to initialize itself");
1160 return NS_ERROR_FAILURE;
1162 if (bidiLevelManager.Canceled()) {
1163 return NS_SUCCESS_DOM_NO_OPERATION;
1166 // AutoRangeArray::ExtendAnchorFocusRangeFor() will use `nsFrameSelection`
1167 // to extend the range for deletion. But if focus event doesn't receive
1168 // yet, ancestor isn't set. So we must set root element of editor to
1169 // ancestor temporarily.
1170 AutoSetTemporaryAncestorLimiter autoSetter(
1171 aHTMLEditor, *aHTMLEditor.SelectionRefPtr(), *startPoint.GetContainer(),
1172 &aRangesToDelete);
1174 Result<nsIEditor::EDirection, nsresult> extendResult =
1175 aRangesToDelete.ExtendAnchorFocusRangeFor(aHTMLEditor,
1176 aDirectionAndAmount);
1177 if (extendResult.isErr()) {
1178 NS_WARNING("AutoRangeArray::ExtendAnchorFocusRangeFor() failed");
1179 return extendResult.unwrapErr();
1182 // For compatibility with other browsers, we should set target ranges
1183 // to start from and/or end after an atomic content rather than start
1184 // from preceding text node end nor end at following text node start.
1185 Result<bool, nsresult> shrunkenResult =
1186 aRangesToDelete.ShrinkRangesIfStartFromOrEndAfterAtomicContent(
1187 aHTMLEditor, aDirectionAndAmount,
1188 AutoRangeArray::IfSelectingOnlyOneAtomicContent::Collapse,
1189 editingHost);
1190 if (shrunkenResult.isErr()) {
1191 NS_WARNING(
1192 "AutoRangeArray::ShrinkRangesIfStartFromOrEndAfterAtomicContent() "
1193 "failed");
1194 return shrunkenResult.unwrapErr();
1197 if (!shrunkenResult.inspect() || !aRangesToDelete.IsCollapsed()) {
1198 aDirectionAndAmount = extendResult.unwrap();
1201 if (aDirectionAndAmount == nsIEditor::eNone) {
1202 MOZ_ASSERT(aRangesToDelete.Ranges().Length() == 1);
1203 if (!CanFallbackToDeleteRangesWithTransaction(aRangesToDelete)) {
1204 // XXX In this case, do we need to modify the range again?
1205 return NS_SUCCESS_DOM_NO_OPERATION;
1207 nsresult rv = FallbackToComputeRangesToDeleteRangesWithTransaction(
1208 aHTMLEditor, aRangesToDelete);
1209 NS_WARNING_ASSERTION(
1210 NS_SUCCEEDED(rv),
1211 "AutoDeleteRangesHandler::"
1212 "FallbackToComputeRangesToDeleteRangesWithTransaction() failed");
1213 return rv;
1216 if (aRangesToDelete.IsCollapsed()) {
1217 EditorDOMPoint caretPoint(aRangesToDelete.GetStartPointOfFirstRange());
1218 if (NS_WARN_IF(!caretPoint.IsInContentNode())) {
1219 return NS_ERROR_FAILURE;
1221 if (!EditorUtils::IsEditableContent(*caretPoint.ContainerAsContent(),
1222 EditorType::HTML)) {
1223 return NS_SUCCESS_DOM_NO_OPERATION;
1225 WSRunScanner wsRunScannerAtCaret(aHTMLEditor, caretPoint);
1226 WSScanResult scanFromCaretPointResult =
1227 aDirectionAndAmount == nsIEditor::eNext
1228 ? wsRunScannerAtCaret.ScanNextVisibleNodeOrBlockBoundaryFrom(
1229 caretPoint)
1230 : wsRunScannerAtCaret.ScanPreviousVisibleNodeOrBlockBoundaryFrom(
1231 caretPoint);
1232 if (scanFromCaretPointResult.Failed()) {
1233 NS_WARNING(
1234 "WSRunScanner::Scan(Next|Previous)VisibleNodeOrBlockBoundaryFrom() "
1235 "failed");
1236 return NS_ERROR_FAILURE;
1238 if (!scanFromCaretPointResult.GetContent()) {
1239 return NS_SUCCESS_DOM_NO_OPERATION;
1242 if (scanFromCaretPointResult.ReachedBRElement()) {
1243 if (scanFromCaretPointResult.BRElementPtr() ==
1244 wsRunScannerAtCaret.GetEditingHost()) {
1245 return NS_OK;
1247 if (!EditorUtils::IsEditableContent(
1248 *scanFromCaretPointResult.BRElementPtr(), EditorType::HTML)) {
1249 return NS_SUCCESS_DOM_NO_OPERATION;
1251 if (!aHTMLEditor.IsVisibleBRElement(
1252 scanFromCaretPointResult.BRElementPtr())) {
1253 EditorDOMPoint newCaretPosition =
1254 aDirectionAndAmount == nsIEditor::eNext
1255 ? EditorDOMPoint::After(
1256 *scanFromCaretPointResult.BRElementPtr())
1257 : EditorDOMPoint(scanFromCaretPointResult.BRElementPtr());
1258 if (NS_WARN_IF(!newCaretPosition.IsSet())) {
1259 return NS_ERROR_FAILURE;
1261 AutoHideSelectionChanges blockSelectionListeners(
1262 aHTMLEditor.SelectionRefPtr());
1263 nsresult rv = aHTMLEditor.CollapseSelectionTo(newCaretPosition);
1264 if (NS_FAILED(rv)) {
1265 NS_WARNING("HTMLEditor::CollapseSelectionTo() failed");
1266 return NS_ERROR_FAILURE;
1268 if (NS_WARN_IF(!aHTMLEditor.SelectionRefPtr()->RangeCount())) {
1269 return NS_ERROR_UNEXPECTED;
1271 aRangesToDelete.Initialize(*aHTMLEditor.SelectionRefPtr());
1272 AutoDeleteRangesHandler anotherHandler(this);
1273 rv = anotherHandler.ComputeRangesToDelete(
1274 aHTMLEditor, aDirectionAndAmount, aRangesToDelete);
1275 NS_WARNING_ASSERTION(
1276 NS_SUCCEEDED(rv),
1277 "Recursive AutoDeleteRangesHandler::ComputeRangesToDelete() "
1278 "failed");
1280 DebugOnly<nsresult> rvIgnored =
1281 aHTMLEditor.CollapseSelectionTo(caretPoint);
1282 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
1283 "HTMLEditor::CollapseSelectionTo() failed to "
1284 "restore original selection");
1286 MOZ_ASSERT(aRangesToDelete.Ranges().Length() == 1);
1287 // If the range is collapsed, there is no content which should
1288 // be removed together. In this case, only the invisible `<br>`
1289 // element should be selected.
1290 if (aRangesToDelete.IsCollapsed()) {
1291 nsresult rv = aRangesToDelete.SelectNode(
1292 *scanFromCaretPointResult.BRElementPtr());
1293 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
1294 "AutoRangeArray::SelectNode() failed");
1295 return rv;
1298 // Otherwise, extend the range to contain the invisible `<br>`
1299 // element.
1300 if (EditorRawDOMPoint(scanFromCaretPointResult.BRElementPtr())
1301 .IsBefore(aRangesToDelete.GetStartPointOfFirstRange())) {
1302 nsresult rv = aRangesToDelete.FirstRangeRef()->SetStartAndEnd(
1303 EditorRawDOMPoint(scanFromCaretPointResult.BRElementPtr())
1304 .ToRawRangeBoundary(),
1305 aRangesToDelete.FirstRangeRef()->EndRef());
1306 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
1307 "nsRange::SetStartAndEnd() failed");
1308 return rv;
1310 if (aRangesToDelete.GetEndPointOfFirstRange().IsBefore(
1311 EditorRawDOMPoint::After(
1312 *scanFromCaretPointResult.BRElementPtr()))) {
1313 nsresult rv = aRangesToDelete.FirstRangeRef()->SetStartAndEnd(
1314 aRangesToDelete.FirstRangeRef()->StartRef(),
1315 EditorRawDOMPoint::After(
1316 *scanFromCaretPointResult.BRElementPtr())
1317 .ToRawRangeBoundary());
1318 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
1319 "nsRange::SetStartAndEnd() failed");
1320 return rv;
1322 NS_WARNING("Was the invisible `<br>` element selected?");
1323 return NS_OK;
1327 nsresult rv = ComputeRangesToDeleteAroundCollapsedRanges(
1328 aHTMLEditor, aDirectionAndAmount, aRangesToDelete,
1329 wsRunScannerAtCaret, scanFromCaretPointResult);
1330 NS_WARNING_ASSERTION(
1331 NS_SUCCEEDED(rv),
1332 "AutoDeleteRangesHandler::ComputeRangesToDeleteAroundCollapsedRanges("
1333 ") failed");
1334 return rv;
1338 nsresult rv = ComputeRangesToDeleteNonCollapsedRanges(
1339 aHTMLEditor, aDirectionAndAmount, aRangesToDelete, selectionWasCollapsed);
1340 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
1341 "AutoDeleteRangesHandler::"
1342 "ComputeRangesToDeleteNonCollapsedRanges() failed");
1343 return rv;
1346 EditActionResult HTMLEditor::AutoDeleteRangesHandler::Run(
1347 HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount,
1348 nsIEditor::EStripWrappers aStripWrappers, AutoRangeArray& aRangesToDelete) {
1349 MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable());
1350 MOZ_ASSERT(aStripWrappers == nsIEditor::eStrip ||
1351 aStripWrappers == nsIEditor::eNoStrip);
1352 MOZ_ASSERT(!aRangesToDelete.Ranges().IsEmpty());
1354 mOriginalDirectionAndAmount = aDirectionAndAmount;
1355 mOriginalStripWrappers = aStripWrappers;
1357 // If there is only padding `<br>` element for empty editor, cancel the
1358 // operation.
1359 if (aHTMLEditor.mPaddingBRElementForEmptyEditor) {
1360 return EditActionCanceled();
1363 // selectionWasCollapsed is used later to determine whether we should join
1364 // blocks in HandleDeleteNonCollapsedRanges(). We don't really care about
1365 // collapsed because it will be modified by
1366 // AutoRangeArray::ExtendAnchorFocusRangeFor() later.
1367 // AutoBlockElementsJoiner::AutoInclusiveAncestorBlockElementsJoiner should
1368 // happen if the original selection is collapsed and the cursor is at the end
1369 // of a block element, in which case
1370 // AutoRangeArray::ExtendAnchorFocusRangeFor() would always make the selection
1371 // not collapsed.
1372 SelectionWasCollapsed selectionWasCollapsed = aRangesToDelete.IsCollapsed()
1373 ? SelectionWasCollapsed::Yes
1374 : SelectionWasCollapsed::No;
1376 if (selectionWasCollapsed == SelectionWasCollapsed::Yes) {
1377 EditorDOMPoint startPoint(aRangesToDelete.GetStartPointOfFirstRange());
1378 if (NS_WARN_IF(!startPoint.IsSet())) {
1379 return EditActionResult(NS_ERROR_FAILURE);
1382 // If we are inside an empty block, delete it.
1383 RefPtr<Element> editingHost = aHTMLEditor.GetActiveEditingHost();
1384 if (startPoint.GetContainerAsContent()) {
1385 if (NS_WARN_IF(!editingHost)) {
1386 return EditActionResult(NS_ERROR_FAILURE);
1388 #ifdef DEBUG
1389 nsMutationGuard debugMutation;
1390 #endif // #ifdef DEBUG
1391 AutoEmptyBlockAncestorDeleter deleter;
1392 if (deleter.ScanEmptyBlockInclusiveAncestor(
1393 aHTMLEditor, *startPoint.GetContainerAsContent(), *editingHost)) {
1394 EditActionResult result = deleter.Run(aHTMLEditor, aDirectionAndAmount);
1395 if (result.Failed() || result.Handled()) {
1396 NS_WARNING_ASSERTION(result.Succeeded(),
1397 "AutoEmptyBlockAncestorDeleter::Run() failed");
1398 return result;
1401 MOZ_ASSERT(!debugMutation.Mutated(0),
1402 "AutoEmptyBlockAncestorDeleter shouldn't modify the DOM tree "
1403 "if it returns not handled nor error");
1406 // Test for distance between caret and text that will be deleted.
1407 // Note that this call modifies `nsFrameSelection` without modifying
1408 // `Selection`. However, it does not have problem for now because
1409 // it'll be referred by `AutoRangeArray::ExtendAnchorFocusRangeFor()`
1410 // before modifying `Selection`.
1411 // XXX This looks odd. `ExtendAnchorFocusRangeFor()` will extend
1412 // anchor-focus range, but here refers the first range.
1413 AutoCaretBidiLevelManager bidiLevelManager(aHTMLEditor, aDirectionAndAmount,
1414 startPoint);
1415 if (bidiLevelManager.Failed()) {
1416 NS_WARNING(
1417 "EditorBase::AutoCaretBidiLevelManager failed to initialize itself");
1418 return EditActionResult(NS_ERROR_FAILURE);
1420 bidiLevelManager.MaybeUpdateCaretBidiLevel(aHTMLEditor);
1421 if (bidiLevelManager.Canceled()) {
1422 return EditActionCanceled();
1425 // AutoRangeArray::ExtendAnchorFocusRangeFor() will use `nsFrameSelection`
1426 // to extend the range for deletion. But if focus event doesn't receive
1427 // yet, ancestor isn't set. So we must set root element of editor to
1428 // ancestor temporarily.
1429 AutoSetTemporaryAncestorLimiter autoSetter(
1430 aHTMLEditor, *aHTMLEditor.SelectionRefPtr(), *startPoint.GetContainer(),
1431 &aRangesToDelete);
1433 // Calling `ExtendAnchorFocusRangeFor()` and
1434 // `ShrinkRangesIfStartFromOrEndAfterAtomicContent()` may move caret to
1435 // the container of deleting atomic content. However, it may be different
1436 // from the original caret's container. The original caret container may
1437 // be important to put caret after deletion so that let's cache the
1438 // original position.
1439 Maybe<EditorDOMPoint> caretPoint;
1440 if (aRangesToDelete.IsCollapsed() && !aRangesToDelete.Ranges().IsEmpty()) {
1441 caretPoint = Some(aRangesToDelete.GetStartPointOfFirstRange());
1442 if (NS_WARN_IF(!caretPoint.ref().IsInContentNode())) {
1443 return EditActionResult(NS_ERROR_FAILURE);
1447 Result<nsIEditor::EDirection, nsresult> extendResult =
1448 aRangesToDelete.ExtendAnchorFocusRangeFor(aHTMLEditor,
1449 aDirectionAndAmount);
1450 if (extendResult.isErr()) {
1451 NS_WARNING("AutoRangeArray::ExtendAnchorFocusRangeFor() failed");
1452 return EditActionResult(extendResult.unwrapErr());
1454 if (caretPoint.isSome() && !caretPoint.ref().IsSetAndValid()) {
1455 NS_WARNING("The caret position became invalid");
1456 return EditActionHandled(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
1459 // If there is only one range and it selects an atomic content, we should
1460 // delete it with collapsed range path for making consistent behavior
1461 // between both cases, the content is selected case and caret is at it or
1462 // after it case.
1463 Result<bool, nsresult> shrunkenResult =
1464 aRangesToDelete.ShrinkRangesIfStartFromOrEndAfterAtomicContent(
1465 aHTMLEditor, aDirectionAndAmount,
1466 AutoRangeArray::IfSelectingOnlyOneAtomicContent::Collapse,
1467 editingHost);
1468 if (shrunkenResult.isErr()) {
1469 NS_WARNING(
1470 "AutoRangeArray::ShrinkRangesIfStartFromOrEndAfterAtomicContent() "
1471 "failed");
1472 return EditActionResult(shrunkenResult.unwrapErr());
1475 if (!shrunkenResult.inspect() || !aRangesToDelete.IsCollapsed()) {
1476 aDirectionAndAmount = extendResult.unwrap();
1479 if (aDirectionAndAmount == nsIEditor::eNone) {
1480 MOZ_ASSERT(aRangesToDelete.Ranges().Length() == 1);
1481 if (!CanFallbackToDeleteRangesWithTransaction(aRangesToDelete)) {
1482 return EditActionIgnored();
1484 EditActionResult result =
1485 FallbackToDeleteRangesWithTransaction(aHTMLEditor, aRangesToDelete);
1486 NS_WARNING_ASSERTION(result.Succeeded(),
1487 "AutoDeleteRangesHandler::"
1488 "FallbackToDeleteRangesWithTransaction() failed");
1489 return result;
1492 if (aRangesToDelete.IsCollapsed()) {
1493 // Use the original caret position for handling the deletion around
1494 // collapsed range because the container may be different from the
1495 // new collapsed position's container.
1496 if (!EditorUtils::IsEditableContent(
1497 *caretPoint.ref().ContainerAsContent(), EditorType::HTML)) {
1498 return EditActionCanceled();
1500 WSRunScanner wsRunScannerAtCaret(aHTMLEditor, caretPoint.ref());
1501 WSScanResult scanFromCaretPointResult =
1502 aDirectionAndAmount == nsIEditor::eNext
1503 ? wsRunScannerAtCaret.ScanNextVisibleNodeOrBlockBoundaryFrom(
1504 caretPoint.ref())
1505 : wsRunScannerAtCaret.ScanPreviousVisibleNodeOrBlockBoundaryFrom(
1506 caretPoint.ref());
1507 if (scanFromCaretPointResult.Failed()) {
1508 NS_WARNING(
1509 "WSRunScanner::Scan(Next|Previous)VisibleNodeOrBlockBoundaryFrom() "
1510 "failed");
1511 return EditActionResult(NS_ERROR_FAILURE);
1513 if (!scanFromCaretPointResult.GetContent()) {
1514 return EditActionCanceled();
1516 // Short circuit for invisible breaks. delete them and recurse.
1517 if (scanFromCaretPointResult.ReachedBRElement()) {
1518 if (scanFromCaretPointResult.BRElementPtr() ==
1519 wsRunScannerAtCaret.GetEditingHost()) {
1520 return EditActionHandled();
1522 if (!EditorUtils::IsEditableContent(
1523 *scanFromCaretPointResult.BRElementPtr(), EditorType::HTML)) {
1524 return EditActionCanceled();
1526 if (!aHTMLEditor.IsVisibleBRElement(
1527 scanFromCaretPointResult.BRElementPtr())) {
1528 // TODO: We should extend the range to delete again before/after
1529 // the caret point and use `HandleDeleteNonCollapsedRanges()`
1530 // instead after we would create delete range computation
1531 // method at switching to the new white-space normalizer.
1532 nsresult rv = WhiteSpaceVisibilityKeeper::
1533 DeleteContentNodeAndJoinTextNodesAroundIt(
1534 aHTMLEditor,
1535 MOZ_KnownLive(*scanFromCaretPointResult.BRElementPtr()),
1536 caretPoint.ref());
1537 if (NS_FAILED(rv)) {
1538 NS_WARNING(
1539 "WhiteSpaceVisibilityKeeper::"
1540 "DeleteContentNodeAndJoinTextNodesAroundIt() failed");
1541 return EditActionHandled(rv);
1543 if (aHTMLEditor.SelectionRefPtr()->RangeCount() != 1) {
1544 NS_WARNING(
1545 "Selection was unexpected after removing an invisible `<br>` "
1546 "element");
1547 return EditActionHandled(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
1549 AutoRangeArray rangesToDelete(*aHTMLEditor.SelectionRefPtr());
1550 caretPoint = Some(aRangesToDelete.GetStartPointOfFirstRange());
1551 if (!caretPoint.ref().IsSet()) {
1552 NS_WARNING(
1553 "New selection after deleting invisible `<br>` element was "
1554 "invalid");
1555 return EditActionHandled(NS_ERROR_FAILURE);
1557 if (aHTMLEditor.MayHaveMutationEventListeners(
1558 NS_EVENT_BITS_MUTATION_SUBTREEMODIFIED |
1559 NS_EVENT_BITS_MUTATION_NODEREMOVED |
1560 NS_EVENT_BITS_MUTATION_NODEREMOVEDFROMDOCUMENT)) {
1561 // Let's check whether there is new invisible `<br>` element
1562 // for avoiding infinit recursive calls.
1563 WSRunScanner wsRunScannerAtCaret(aHTMLEditor, caretPoint.ref());
1564 WSScanResult scanFromCaretPointResult =
1565 aDirectionAndAmount == nsIEditor::eNext
1566 ? wsRunScannerAtCaret
1567 .ScanNextVisibleNodeOrBlockBoundaryFrom(
1568 caretPoint.ref())
1569 : wsRunScannerAtCaret
1570 .ScanPreviousVisibleNodeOrBlockBoundaryFrom(
1571 caretPoint.ref());
1572 if (scanFromCaretPointResult.Failed()) {
1573 NS_WARNING(
1574 "WSRunScanner::Scan(Next|Previous)"
1575 "VisibleNodeOrBlockBoundaryFrom() failed");
1576 return EditActionResult(NS_ERROR_FAILURE);
1578 if (scanFromCaretPointResult.ReachedBRElement() &&
1579 !aHTMLEditor.IsVisibleBRElement(
1580 scanFromCaretPointResult.BRElementPtr())) {
1581 return EditActionHandled(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
1584 AutoDeleteRangesHandler anotherHandler(this);
1585 EditActionResult result = anotherHandler.Run(
1586 aHTMLEditor, aDirectionAndAmount, aStripWrappers, rangesToDelete);
1587 NS_WARNING_ASSERTION(
1588 result.Succeeded(),
1589 "Recursive AutoDeleteRangesHandler::Run() failed");
1590 return result;
1594 EditActionResult result = HandleDeleteAroundCollapsedRanges(
1595 aHTMLEditor, aDirectionAndAmount, aStripWrappers, aRangesToDelete,
1596 wsRunScannerAtCaret, scanFromCaretPointResult);
1597 NS_WARNING_ASSERTION(result.Succeeded(),
1598 "AutoDeleteRangesHandler::"
1599 "HandleDeleteAroundCollapsedRanges() failed");
1600 return result;
1604 EditActionResult result = HandleDeleteNonCollapsedRanges(
1605 aHTMLEditor, aDirectionAndAmount, aStripWrappers, aRangesToDelete,
1606 selectionWasCollapsed);
1607 NS_WARNING_ASSERTION(
1608 result.Succeeded(),
1609 "AutoDeleteRangesHandler::HandleDeleteNonCollapsedRanges() failed");
1610 return result;
1613 nsresult
1614 HTMLEditor::AutoDeleteRangesHandler::ComputeRangesToDeleteAroundCollapsedRanges(
1615 const HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount,
1616 AutoRangeArray& aRangesToDelete, const WSRunScanner& aWSRunScannerAtCaret,
1617 const WSScanResult& aScanFromCaretPointResult) const {
1618 if (aScanFromCaretPointResult.InNormalWhiteSpaces() ||
1619 aScanFromCaretPointResult.InNormalText()) {
1620 nsresult rv = aRangesToDelete.Collapse(aScanFromCaretPointResult.Point());
1621 if (NS_FAILED(rv)) {
1622 NS_WARNING("AutoRangeArray::Collapse() failed");
1623 return NS_ERROR_FAILURE;
1625 rv = ComputeRangesToDeleteTextAroundCollapsedRanges(
1626 aHTMLEditor, aDirectionAndAmount, aRangesToDelete);
1627 NS_WARNING_ASSERTION(
1628 NS_SUCCEEDED(rv),
1629 "AutoDeleteRangesHandler::"
1630 "ComputeRangesToDeleteTextAroundCollapsedRanges() failed");
1631 return rv;
1634 if (aScanFromCaretPointResult.ReachedSpecialContent() ||
1635 aScanFromCaretPointResult.ReachedBRElement()) {
1636 if (aScanFromCaretPointResult.GetContent() ==
1637 aWSRunScannerAtCaret.GetEditingHost()) {
1638 return NS_OK;
1640 nsresult rv = ComputeRangesToDeleteAtomicContent(
1641 aHTMLEditor, *aScanFromCaretPointResult.GetContent(), aRangesToDelete);
1642 NS_WARNING_ASSERTION(
1643 NS_SUCCEEDED(rv),
1644 "AutoDeleteRangesHandler::ComputeRangesToDeleteAtomicContent() failed");
1645 return rv;
1648 if (aScanFromCaretPointResult.ReachedHRElement()) {
1649 if (aScanFromCaretPointResult.GetContent() ==
1650 aWSRunScannerAtCaret.GetEditingHost()) {
1651 return NS_OK;
1653 nsresult rv =
1654 ComputeRangesToDeleteHRElement(aHTMLEditor, aDirectionAndAmount,
1655 *aScanFromCaretPointResult.ElementPtr(),
1656 aWSRunScannerAtCaret.ScanStartRef(),
1657 aWSRunScannerAtCaret, aRangesToDelete);
1658 NS_WARNING_ASSERTION(
1659 NS_SUCCEEDED(rv),
1660 "AutoDeleteRangesHandler::ComputeRangesToDeleteHRElement() failed");
1661 return rv;
1664 if (aScanFromCaretPointResult.ReachedOtherBlockElement()) {
1665 if (NS_WARN_IF(!aScanFromCaretPointResult.GetContent()->IsElement())) {
1666 return NS_ERROR_FAILURE;
1668 AutoBlockElementsJoiner joiner(*this);
1669 if (!joiner.PrepareToDeleteAtOtherBlockBoundary(
1670 aHTMLEditor, aDirectionAndAmount,
1671 *aScanFromCaretPointResult.ElementPtr(),
1672 aWSRunScannerAtCaret.ScanStartRef(), aWSRunScannerAtCaret)) {
1673 return NS_SUCCESS_DOM_NO_OPERATION;
1675 nsresult rv = joiner.ComputeRangesToDelete(
1676 aHTMLEditor, aDirectionAndAmount, aWSRunScannerAtCaret.ScanStartRef(),
1677 aRangesToDelete);
1678 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
1679 "AutoBlockElementsJoiner::ComputeRangesToDelete() "
1680 "failed (other block boundary)");
1681 return rv;
1684 if (aScanFromCaretPointResult.ReachedCurrentBlockBoundary()) {
1685 if (NS_WARN_IF(!aScanFromCaretPointResult.GetContent()->IsElement())) {
1686 return NS_ERROR_FAILURE;
1688 AutoBlockElementsJoiner joiner(*this);
1689 if (!joiner.PrepareToDeleteAtCurrentBlockBoundary(
1690 aHTMLEditor, aDirectionAndAmount,
1691 *aScanFromCaretPointResult.ElementPtr(),
1692 aWSRunScannerAtCaret.ScanStartRef())) {
1693 return NS_SUCCESS_DOM_NO_OPERATION;
1695 nsresult rv = joiner.ComputeRangesToDelete(
1696 aHTMLEditor, aDirectionAndAmount, aWSRunScannerAtCaret.ScanStartRef(),
1697 aRangesToDelete);
1698 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
1699 "AutoBlockElementsJoiner::ComputeRangesToDelete() "
1700 "failed (current block boundary)");
1701 return rv;
1704 return NS_OK;
1707 EditActionResult
1708 HTMLEditor::AutoDeleteRangesHandler::HandleDeleteAroundCollapsedRanges(
1709 HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount,
1710 nsIEditor::EStripWrappers aStripWrappers, AutoRangeArray& aRangesToDelete,
1711 const WSRunScanner& aWSRunScannerAtCaret,
1712 const WSScanResult& aScanFromCaretPointResult) {
1713 MOZ_ASSERT(aHTMLEditor.IsTopLevelEditSubActionDataAvailable());
1714 MOZ_ASSERT(aRangesToDelete.IsCollapsed());
1715 MOZ_ASSERT(aDirectionAndAmount != nsIEditor::eNone);
1716 MOZ_ASSERT(aWSRunScannerAtCaret.ScanStartRef().IsInContentNode());
1717 MOZ_ASSERT(EditorUtils::IsEditableContent(
1718 *aWSRunScannerAtCaret.ScanStartRef().ContainerAsContent(),
1719 EditorType::HTML));
1721 if (StaticPrefs::editor_white_space_normalization_blink_compatible()) {
1722 if (aScanFromCaretPointResult.InNormalWhiteSpaces() ||
1723 aScanFromCaretPointResult.InNormalText()) {
1724 nsresult rv = aRangesToDelete.Collapse(aScanFromCaretPointResult.Point());
1725 if (NS_FAILED(rv)) {
1726 NS_WARNING("AutoRangeArray::Collapse() failed");
1727 return EditActionResult(NS_ERROR_FAILURE);
1729 EditActionResult result = HandleDeleteTextAroundCollapsedRanges(
1730 aHTMLEditor, aDirectionAndAmount, aRangesToDelete);
1731 NS_WARNING_ASSERTION(result.Succeeded(),
1732 "AutoDeleteRangesHandler::"
1733 "HandleDeleteTextAroundCollapsedRanges() failed");
1734 return result;
1738 if (aScanFromCaretPointResult.InNormalWhiteSpaces()) {
1739 EditActionResult result = HandleDeleteCollapsedSelectionAtWhiteSpaces(
1740 aHTMLEditor, aDirectionAndAmount, aWSRunScannerAtCaret.ScanStartRef());
1741 NS_WARNING_ASSERTION(result.Succeeded(),
1742 "AutoDeleteRangesHandler::"
1743 "HandleDelectCollapsedSelectionAtWhiteSpaces() "
1744 "failed");
1745 return result;
1748 if (aScanFromCaretPointResult.InNormalText()) {
1749 if (NS_WARN_IF(!aScanFromCaretPointResult.GetContent()->IsText())) {
1750 return EditActionResult(NS_ERROR_FAILURE);
1752 EditActionResult result = HandleDeleteCollapsedSelectionAtVisibleChar(
1753 aHTMLEditor, aDirectionAndAmount, aScanFromCaretPointResult.Point());
1754 NS_WARNING_ASSERTION(result.Succeeded(),
1755 "AutoDeleteRangesHandler::"
1756 "HandleDeleteCollapsedSelectionAtVisibleChar() "
1757 "failed");
1758 return result;
1761 if (aScanFromCaretPointResult.ReachedSpecialContent() ||
1762 aScanFromCaretPointResult.ReachedBRElement()) {
1763 if (aScanFromCaretPointResult.GetContent() ==
1764 aWSRunScannerAtCaret.GetEditingHost()) {
1765 return EditActionHandled();
1767 EditActionResult result = HandleDeleteAtomicContent(
1768 aHTMLEditor, MOZ_KnownLive(*aScanFromCaretPointResult.GetContent()),
1769 aWSRunScannerAtCaret.ScanStartRef(), aWSRunScannerAtCaret);
1770 NS_WARNING_ASSERTION(
1771 result.Succeeded(),
1772 "AutoDeleteRangesHandler::HandleDeleteAtomicContent() failed");
1773 return result;
1776 if (aScanFromCaretPointResult.ReachedHRElement()) {
1777 if (aScanFromCaretPointResult.GetContent() ==
1778 aWSRunScannerAtCaret.GetEditingHost()) {
1779 return EditActionHandled();
1781 EditActionResult result = HandleDeleteHRElement(
1782 aHTMLEditor, aDirectionAndAmount,
1783 MOZ_KnownLive(*aScanFromCaretPointResult.ElementPtr()),
1784 aWSRunScannerAtCaret.ScanStartRef(), aWSRunScannerAtCaret);
1785 NS_WARNING_ASSERTION(
1786 result.Succeeded(),
1787 "AutoDeleteRangesHandler::HandleDeleteHRElement() failed");
1788 return result;
1791 if (aScanFromCaretPointResult.ReachedOtherBlockElement()) {
1792 if (NS_WARN_IF(!aScanFromCaretPointResult.GetContent()->IsElement())) {
1793 return EditActionResult(NS_ERROR_FAILURE);
1795 AutoBlockElementsJoiner joiner(*this);
1796 if (!joiner.PrepareToDeleteAtOtherBlockBoundary(
1797 aHTMLEditor, aDirectionAndAmount,
1798 *aScanFromCaretPointResult.ElementPtr(),
1799 aWSRunScannerAtCaret.ScanStartRef(), aWSRunScannerAtCaret)) {
1800 return EditActionCanceled();
1802 EditActionResult result =
1803 joiner.Run(aHTMLEditor, aDirectionAndAmount, aStripWrappers,
1804 aWSRunScannerAtCaret.ScanStartRef(), aRangesToDelete);
1805 NS_WARNING_ASSERTION(
1806 result.Succeeded(),
1807 "AutoBlockElementsJoiner::Run() failed (other block boundary)");
1808 return result;
1811 if (aScanFromCaretPointResult.ReachedCurrentBlockBoundary()) {
1812 if (NS_WARN_IF(!aScanFromCaretPointResult.GetContent()->IsElement())) {
1813 return EditActionResult(NS_ERROR_FAILURE);
1815 AutoBlockElementsJoiner joiner(*this);
1816 if (!joiner.PrepareToDeleteAtCurrentBlockBoundary(
1817 aHTMLEditor, aDirectionAndAmount,
1818 *aScanFromCaretPointResult.ElementPtr(),
1819 aWSRunScannerAtCaret.ScanStartRef())) {
1820 return EditActionCanceled();
1822 EditActionResult result =
1823 joiner.Run(aHTMLEditor, aDirectionAndAmount, aStripWrappers,
1824 aWSRunScannerAtCaret.ScanStartRef(), aRangesToDelete);
1825 NS_WARNING_ASSERTION(
1826 result.Succeeded(),
1827 "AutoBlockElementsJoiner::Run() failed (current block boundary)");
1828 return result;
1831 MOZ_ASSERT_UNREACHABLE("New type of reached content hasn't been handled yet");
1832 return EditActionIgnored();
1835 nsresult HTMLEditor::AutoDeleteRangesHandler::
1836 ComputeRangesToDeleteTextAroundCollapsedRanges(
1837 const HTMLEditor& aHTMLEditor,
1838 nsIEditor::EDirection aDirectionAndAmount,
1839 AutoRangeArray& aRangesToDelete) const {
1840 MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable());
1841 MOZ_ASSERT(aDirectionAndAmount == nsIEditor::eNext ||
1842 aDirectionAndAmount == nsIEditor::ePrevious);
1844 EditorDOMPoint caretPosition(aRangesToDelete.GetStartPointOfFirstRange());
1845 MOZ_ASSERT(caretPosition.IsSetAndValid());
1846 if (NS_WARN_IF(!caretPosition.IsInContentNode())) {
1847 return NS_ERROR_FAILURE;
1850 EditorDOMRangeInTexts rangeToDelete;
1851 if (aDirectionAndAmount == nsIEditor::eNext) {
1852 Result<EditorDOMRangeInTexts, nsresult> result =
1853 WSRunScanner::GetRangeInTextNodesToForwardDeleteFrom(aHTMLEditor,
1854 caretPosition);
1855 if (result.isErr()) {
1856 NS_WARNING(
1857 "WSRunScanner::GetRangeInTextNodesToForwardDeleteFrom() failed");
1858 return result.unwrapErr();
1860 rangeToDelete = result.unwrap();
1861 if (!rangeToDelete.IsPositioned()) {
1862 return NS_OK; // no range to delete, but consume it.
1864 } else {
1865 Result<EditorDOMRangeInTexts, nsresult> result =
1866 WSRunScanner::GetRangeInTextNodesToBackspaceFrom(aHTMLEditor,
1867 caretPosition);
1868 if (result.isErr()) {
1869 NS_WARNING("WSRunScanner::GetRangeInTextNodesToBackspaceFrom() failed");
1870 return result.unwrapErr();
1872 rangeToDelete = result.unwrap();
1873 if (!rangeToDelete.IsPositioned()) {
1874 return NS_OK; // no range to delete, but consume it.
1878 nsresult rv = aRangesToDelete.SetStartAndEnd(rangeToDelete.StartRef(),
1879 rangeToDelete.EndRef());
1880 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
1881 "AutoArrayRanges::SetStartAndEnd() failed");
1882 return rv;
1885 EditActionResult
1886 HTMLEditor::AutoDeleteRangesHandler::HandleDeleteTextAroundCollapsedRanges(
1887 HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount,
1888 AutoRangeArray& aRangesToDelete) {
1889 MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable());
1890 MOZ_ASSERT(aDirectionAndAmount == nsIEditor::eNext ||
1891 aDirectionAndAmount == nsIEditor::ePrevious);
1893 nsresult rv = ComputeRangesToDeleteTextAroundCollapsedRanges(
1894 aHTMLEditor, aDirectionAndAmount, aRangesToDelete);
1895 if (NS_FAILED(rv)) {
1896 return EditActionResult(NS_ERROR_FAILURE);
1898 if (aRangesToDelete.IsCollapsed()) {
1899 return EditActionHandled(); // no range to delete
1902 // FYI: rangeToDelete does not contain newly empty inline ancestors which
1903 // are removed by DeleteTextAndNormalizeSurroundingWhiteSpaces().
1904 // So, if `getTargetRanges()` needs to include parent empty elements,
1905 // we need to extend the range with
1906 // HTMLEditUtils::GetMostDistantAnscestorEditableEmptyInlineElement().
1907 EditorRawDOMRange rangeToDelete(aRangesToDelete.FirstRangeRef());
1908 if (!rangeToDelete.IsInTextNodes()) {
1909 NS_WARNING("The extended range to delete character was not in text nodes");
1910 return EditActionResult(NS_ERROR_FAILURE);
1913 AutoTransactionsConserveSelection dontChangeMySelection(aHTMLEditor);
1914 Result<EditorDOMPoint, nsresult> result =
1915 aHTMLEditor.DeleteTextAndNormalizeSurroundingWhiteSpaces(
1916 rangeToDelete.StartRef().AsInText(),
1917 rangeToDelete.EndRef().AsInText(),
1918 TreatEmptyTextNodes::RemoveAllEmptyInlineAncestors,
1919 aDirectionAndAmount == nsIEditor::eNext ? DeleteDirection::Forward
1920 : DeleteDirection::Backward);
1921 aHTMLEditor.TopLevelEditSubActionDataRef().mDidNormalizeWhitespaces = true;
1922 if (result.isErr()) {
1923 NS_WARNING(
1924 "HTMLEditor::DeleteTextAndNormalizeSurroundingWhiteSpaces() failed");
1925 return EditActionHandled(result.unwrapErr());
1927 const EditorDOMPoint& newCaretPosition = result.inspect();
1928 MOZ_ASSERT(newCaretPosition.IsSetAndValid());
1930 DebugOnly<nsresult> rvIgnored =
1931 MOZ_KnownLive(aHTMLEditor.SelectionRefPtr())
1932 ->CollapseInLimiter(newCaretPosition.ToRawRangeBoundary());
1933 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
1934 "Selection::Collapse() failed, but ignored");
1935 return EditActionHandled();
1938 EditActionResult HTMLEditor::AutoDeleteRangesHandler::
1939 HandleDeleteCollapsedSelectionAtWhiteSpaces(
1940 HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount,
1941 const EditorDOMPoint& aPointToDelete) {
1942 MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable());
1943 MOZ_ASSERT(!StaticPrefs::editor_white_space_normalization_blink_compatible());
1945 if (aDirectionAndAmount == nsIEditor::eNext) {
1946 nsresult rv = WhiteSpaceVisibilityKeeper::DeleteInclusiveNextWhiteSpace(
1947 aHTMLEditor, aPointToDelete);
1948 if (NS_FAILED(rv)) {
1949 NS_WARNING(
1950 "WhiteSpaceVisibilityKeeper::DeleteInclusiveNextWhiteSpace() failed");
1951 return EditActionHandled(rv);
1953 } else {
1954 nsresult rv = WhiteSpaceVisibilityKeeper::DeletePreviousWhiteSpace(
1955 aHTMLEditor, aPointToDelete);
1956 if (NS_FAILED(rv)) {
1957 NS_WARNING(
1958 "WhiteSpaceVisibilityKeeper::DeletePreviousWhiteSpace() failed");
1959 return EditActionHandled(rv);
1962 EditorDOMPoint newCaretPosition =
1963 EditorBase::GetStartPoint(*aHTMLEditor.SelectionRefPtr());
1964 if (!newCaretPosition.IsSet()) {
1965 NS_WARNING("There was no selection range");
1966 return EditActionHandled(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
1968 nsresult rv =
1969 aHTMLEditor.InsertBRElementIfHardLineIsEmptyAndEndsWithBlockBoundary(
1970 newCaretPosition);
1971 NS_WARNING_ASSERTION(
1972 NS_SUCCEEDED(rv),
1973 "HTMLEditor::InsertBRElementIfHardLineIsEmptyAndEndsWithBlockBoundary() "
1974 "failed");
1975 return EditActionHandled(rv);
1978 EditActionResult HTMLEditor::AutoDeleteRangesHandler::
1979 HandleDeleteCollapsedSelectionAtVisibleChar(
1980 HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount,
1981 const EditorDOMPoint& aPointToDelete) {
1982 MOZ_ASSERT(aHTMLEditor.IsTopLevelEditSubActionDataAvailable());
1983 MOZ_ASSERT(!StaticPrefs::editor_white_space_normalization_blink_compatible());
1984 MOZ_ASSERT(aPointToDelete.IsSet());
1985 MOZ_ASSERT(aPointToDelete.IsInTextNode());
1987 OwningNonNull<Text> visibleTextNode = *aPointToDelete.GetContainerAsText();
1988 EditorDOMPoint startToDelete, endToDelete;
1989 if (aDirectionAndAmount == nsIEditor::ePrevious) {
1990 if (aPointToDelete.IsStartOfContainer()) {
1991 return EditActionResult(NS_ERROR_UNEXPECTED);
1993 startToDelete = aPointToDelete.PreviousPoint();
1994 endToDelete = aPointToDelete;
1995 // Bug 1068979: delete both codepoints if surrogate pair
1996 if (!startToDelete.IsStartOfContainer()) {
1997 const nsTextFragment* text = &visibleTextNode->TextFragment();
1998 if (text->IsLowSurrogateFollowingHighSurrogateAt(
1999 startToDelete.Offset())) {
2000 startToDelete.RewindOffset();
2003 } else {
2004 RefPtr<const nsRange> range = aHTMLEditor.SelectionRefPtr()->GetRangeAt(0);
2005 if (NS_WARN_IF(!range) ||
2006 NS_WARN_IF(range->GetStartContainer() !=
2007 aPointToDelete.GetContainer()) ||
2008 NS_WARN_IF(range->GetEndContainer() != aPointToDelete.GetContainer())) {
2009 return EditActionResult(NS_ERROR_FAILURE);
2011 startToDelete = range->StartRef();
2012 endToDelete = range->EndRef();
2014 nsresult rv = WhiteSpaceVisibilityKeeper::PrepareToDeleteRangeAndTrackPoints(
2015 aHTMLEditor, &startToDelete, &endToDelete);
2016 if (NS_WARN_IF(aHTMLEditor.Destroyed())) {
2017 return EditActionResult(NS_ERROR_EDITOR_DESTROYED);
2019 if (NS_FAILED(rv)) {
2020 NS_WARNING(
2021 "WhiteSpaceVisibilityKeeper::PrepareToDeleteRangeAndTrackPoints() "
2022 "failed");
2023 return EditActionResult(rv);
2025 if (aHTMLEditor.MayHaveMutationEventListeners(
2026 NS_EVENT_BITS_MUTATION_NODEREMOVED |
2027 NS_EVENT_BITS_MUTATION_NODEREMOVEDFROMDOCUMENT |
2028 NS_EVENT_BITS_MUTATION_ATTRMODIFIED |
2029 NS_EVENT_BITS_MUTATION_CHARACTERDATAMODIFIED) &&
2030 (NS_WARN_IF(!startToDelete.IsSetAndValid()) ||
2031 NS_WARN_IF(!startToDelete.IsInTextNode()) ||
2032 NS_WARN_IF(!endToDelete.IsSetAndValid()) ||
2033 NS_WARN_IF(!endToDelete.IsInTextNode()) ||
2034 NS_WARN_IF(startToDelete.ContainerAsText() != visibleTextNode) ||
2035 NS_WARN_IF(endToDelete.ContainerAsText() != visibleTextNode) ||
2036 NS_WARN_IF(startToDelete.Offset() >= endToDelete.Offset()))) {
2037 NS_WARNING("Mutation event listener changed the DOM tree");
2038 return EditActionHandled(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
2040 rv = aHTMLEditor.DeleteTextWithTransaction(
2041 visibleTextNode, startToDelete.Offset(),
2042 endToDelete.Offset() - startToDelete.Offset());
2043 if (NS_WARN_IF(aHTMLEditor.Destroyed())) {
2044 return EditActionHandled(NS_ERROR_EDITOR_DESTROYED);
2046 if (NS_FAILED(rv)) {
2047 NS_WARNING("HTMLEditor::DeleteTextWithTransaction() failed");
2048 return EditActionHandled(rv);
2051 // XXX When Backspace key is pressed, Chromium removes following empty
2052 // text nodes when removing the last character of the non-empty text
2053 // node. However, Edge never removes empty text nodes even if
2054 // selection is in the following empty text node(s). For now, we
2055 // should keep our traditional behavior same as Edge for backward
2056 // compatibility.
2057 // XXX When Delete key is pressed, Edge removes all preceding empty
2058 // text nodes when removing the first character of the non-empty
2059 // text node. Chromium removes only selected empty text node and
2060 // following empty text nodes and the first character of the
2061 // non-empty text node. For now, we should keep our traditional
2062 // behavior same as Chromium for backward compatibility.
2064 rv = DeleteNodeIfInvisibleAndEditableTextNode(aHTMLEditor, visibleTextNode);
2065 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
2066 return EditActionHandled(NS_ERROR_EDITOR_DESTROYED);
2068 NS_WARNING_ASSERTION(
2069 NS_SUCCEEDED(rv),
2070 "AutoDeleteRangesHandler::DeleteNodeIfInvisibleAndEditableTextNode() "
2071 "failed, but ignored");
2073 EditorDOMPoint newCaretPosition =
2074 EditorBase::GetStartPoint(*aHTMLEditor.SelectionRefPtr());
2075 if (!newCaretPosition.IsSet()) {
2076 NS_WARNING("There was no selection range");
2077 return EditActionHandled(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
2080 // XXX `Selection` may be modified by mutation event listeners so
2081 // that we should use EditorDOMPoint::AtEndOf(visibleTextNode)
2082 // instead. (Perhaps, we don't and/or shouldn't need to do this
2083 // if the text node is preformatted.)
2084 rv = aHTMLEditor.InsertBRElementIfHardLineIsEmptyAndEndsWithBlockBoundary(
2085 newCaretPosition);
2086 if (NS_FAILED(rv)) {
2087 NS_WARNING(
2088 "HTMLEditor::InsertBRElementIfHardLineIsEmptyAndEndsWithBlockBoundary()"
2089 " failed");
2090 return EditActionHandled(rv);
2093 // Remember that we did a ranged delete for the benefit of
2094 // AfterEditInner().
2095 aHTMLEditor.TopLevelEditSubActionDataRef().mDidDeleteNonCollapsedRange = true;
2097 return EditActionHandled();
2100 Result<bool, nsresult>
2101 HTMLEditor::AutoDeleteRangesHandler::ShouldDeleteHRElement(
2102 const HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount,
2103 Element& aHRElement, const EditorDOMPoint& aCaretPoint) const {
2104 MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable());
2106 if (StaticPrefs::editor_hr_element_allow_to_delete_from_following_line()) {
2107 return true;
2110 if (aDirectionAndAmount != nsIEditor::ePrevious) {
2111 return true;
2114 // Only if the caret is positioned at the end-of-hr-line position, we
2115 // want to delete the <hr>.
2117 // In other words, we only want to delete, if our selection position
2118 // (indicated by aCaretPoint) is the position directly
2119 // after the <hr>, on the same line as the <hr>.
2121 // To detect this case we check:
2122 // aCaretPoint's container == parent of `<hr>` element
2123 // and
2124 // aCaretPoint's offset -1 == `<hr>` element offset
2125 // and
2126 // interline position is false (left)
2128 // In any other case we set the position to aCaretPoint's container -1
2129 // and interlineposition to false, only moving the caret to the
2130 // end-of-hr-line position.
2131 EditorRawDOMPoint atHRElement(&aHRElement);
2133 ErrorResult error;
2134 bool interLineIsRight =
2135 aHTMLEditor.SelectionRefPtr()->GetInterlinePosition(error);
2136 if (error.Failed()) {
2137 NS_WARNING("Selection::GetInterlinePosition() failed");
2138 nsresult rv = error.StealNSResult();
2139 return Err(rv);
2142 return !interLineIsRight &&
2143 aCaretPoint.GetContainer() == atHRElement.GetContainer() &&
2144 aCaretPoint.Offset() - 1 == atHRElement.Offset();
2147 nsresult HTMLEditor::AutoDeleteRangesHandler::ComputeRangesToDeleteHRElement(
2148 const HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount,
2149 Element& aHRElement, const EditorDOMPoint& aCaretPoint,
2150 const WSRunScanner& aWSRunScannerAtCaret,
2151 AutoRangeArray& aRangesToDelete) const {
2152 MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable());
2153 MOZ_ASSERT(aHRElement.IsHTMLElement(nsGkAtoms::hr));
2154 MOZ_ASSERT(&aHRElement != aWSRunScannerAtCaret.GetEditingHost());
2156 Result<bool, nsresult> canDeleteHRElement = ShouldDeleteHRElement(
2157 aHTMLEditor, aDirectionAndAmount, aHRElement, aCaretPoint);
2158 if (canDeleteHRElement.isErr()) {
2159 NS_WARNING("AutoDeleteRangesHandler::ShouldDeleteHRElement() failed");
2160 return canDeleteHRElement.unwrapErr();
2162 if (canDeleteHRElement.inspect()) {
2163 nsresult rv = ComputeRangesToDeleteAtomicContent(aHTMLEditor, aHRElement,
2164 aRangesToDelete);
2165 NS_WARNING_ASSERTION(
2166 NS_SUCCEEDED(rv),
2167 "AutoDeleteRangesHandler::ComputeRangesToDeleteAtomicContent() failed");
2168 return rv;
2171 WSScanResult forwardScanFromCaretResult =
2172 aWSRunScannerAtCaret.ScanNextVisibleNodeOrBlockBoundaryFrom(aCaretPoint);
2173 if (forwardScanFromCaretResult.Failed()) {
2174 NS_WARNING("WSRunScanner::ScanNextVisibleNodeOrBlockBoundaryFrom() failed");
2175 return NS_ERROR_FAILURE;
2177 if (!forwardScanFromCaretResult.ReachedBRElement()) {
2178 // Restore original caret position if we won't delete anyting.
2179 nsresult rv = aRangesToDelete.Collapse(aCaretPoint);
2180 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "AutoRangeArray::Collapse() failed");
2181 return rv;
2184 // If we'll just move caret position, but if it's followed by a `<br>`
2185 // element, we'll delete it.
2186 nsresult rv = ComputeRangesToDeleteAtomicContent(
2187 aHTMLEditor, *forwardScanFromCaretResult.ElementPtr(), aRangesToDelete);
2188 NS_WARNING_ASSERTION(
2189 NS_SUCCEEDED(rv),
2190 "AutoDeleteRangesHandler::ComputeRangesToDeleteAtomicContent() failed");
2191 return rv;
2194 EditActionResult HTMLEditor::AutoDeleteRangesHandler::HandleDeleteHRElement(
2195 HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount,
2196 Element& aHRElement, const EditorDOMPoint& aCaretPoint,
2197 const WSRunScanner& aWSRunScannerAtCaret) {
2198 MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable());
2199 MOZ_ASSERT(aHRElement.IsHTMLElement(nsGkAtoms::hr));
2200 MOZ_ASSERT(&aHRElement != aWSRunScannerAtCaret.GetEditingHost());
2202 Result<bool, nsresult> canDeleteHRElement = ShouldDeleteHRElement(
2203 aHTMLEditor, aDirectionAndAmount, aHRElement, aCaretPoint);
2204 if (canDeleteHRElement.isErr()) {
2205 NS_WARNING("AutoDeleteRangesHandler::ShouldDeleteHRElement() failed");
2206 return EditActionHandled(canDeleteHRElement.unwrapErr());
2208 if (canDeleteHRElement.inspect()) {
2209 EditActionResult result = HandleDeleteAtomicContent(
2210 aHTMLEditor, aHRElement, aCaretPoint, aWSRunScannerAtCaret);
2211 NS_WARNING_ASSERTION(
2212 result.Succeeded(),
2213 "AutoDeleteRangesHandler::HandleDeleteAtomicContent() failed");
2214 return result;
2217 // Go to the position after the <hr>, but to the end of the <hr> line
2218 // by setting the interline position to left.
2219 EditorDOMPoint atNextOfHRElement(EditorDOMPoint::After(aHRElement));
2220 NS_WARNING_ASSERTION(atNextOfHRElement.IsSet(),
2221 "Failed to set after <hr> element");
2224 AutoEditorDOMPointChildInvalidator lockOffset(atNextOfHRElement);
2226 nsresult rv = aHTMLEditor.CollapseSelectionTo(atNextOfHRElement);
2227 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
2228 return EditActionResult(NS_ERROR_EDITOR_DESTROYED);
2230 NS_WARNING_ASSERTION(
2231 NS_SUCCEEDED(rv),
2232 "HTMLEditor::CollapseSelectionTo() failed, but ignored");
2235 IgnoredErrorResult ignoredError;
2236 aHTMLEditor.SelectionRefPtr()->SetInterlinePosition(false, ignoredError);
2237 NS_WARNING_ASSERTION(
2238 !ignoredError.Failed(),
2239 "Selection::SetInterlinePosition(false) failed, but ignored");
2240 aHTMLEditor.TopLevelEditSubActionDataRef().mDidExplicitlySetInterLine = true;
2242 // There is one exception to the move only case. If the <hr> is
2243 // followed by a <br> we want to delete the <br>.
2245 WSScanResult forwardScanFromCaretResult =
2246 aWSRunScannerAtCaret.ScanNextVisibleNodeOrBlockBoundaryFrom(aCaretPoint);
2247 if (forwardScanFromCaretResult.Failed()) {
2248 NS_WARNING("WSRunScanner::ScanNextVisibleNodeOrBlockBoundaryFrom() failed");
2249 return EditActionResult(NS_ERROR_FAILURE);
2251 if (!forwardScanFromCaretResult.ReachedBRElement()) {
2252 return EditActionHandled();
2255 // Delete the <br>
2256 nsresult rv =
2257 WhiteSpaceVisibilityKeeper::DeleteContentNodeAndJoinTextNodesAroundIt(
2258 aHTMLEditor,
2259 MOZ_KnownLive(*forwardScanFromCaretResult.BRElementPtr()),
2260 aCaretPoint);
2261 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
2262 "WhiteSpaceVisibilityKeeper::"
2263 "DeleteContentNodeAndJoinTextNodesAroundIt() failed");
2264 return EditActionHandled(rv);
2267 nsresult
2268 HTMLEditor::AutoDeleteRangesHandler::ComputeRangesToDeleteAtomicContent(
2269 const HTMLEditor& aHTMLEditor, const nsIContent& aAtomicContent,
2270 AutoRangeArray& aRangesToDelete) const {
2271 EditorDOMRange rangeToDelete =
2272 WSRunScanner::GetRangesForDeletingAtomicContent(aHTMLEditor,
2273 aAtomicContent);
2274 if (!rangeToDelete.IsPositioned()) {
2275 NS_WARNING("WSRunScanner::GetRangeForDeleteAContentNode() failed");
2276 return NS_ERROR_FAILURE;
2278 nsresult rv = aRangesToDelete.SetStartAndEnd(rangeToDelete.StartRef(),
2279 rangeToDelete.EndRef());
2280 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
2281 "AutoRangeArray::SetStartAndEnd() failed");
2282 return rv;
2285 EditActionResult HTMLEditor::AutoDeleteRangesHandler::HandleDeleteAtomicContent(
2286 HTMLEditor& aHTMLEditor, nsIContent& aAtomicContent,
2287 const EditorDOMPoint& aCaretPoint,
2288 const WSRunScanner& aWSRunScannerAtCaret) {
2289 MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable());
2290 MOZ_ASSERT_IF(aAtomicContent.IsHTMLElement(nsGkAtoms::br),
2291 aHTMLEditor.IsVisibleBRElement(&aAtomicContent));
2292 MOZ_ASSERT(&aAtomicContent != aWSRunScannerAtCaret.GetEditingHost());
2294 nsresult rv =
2295 WhiteSpaceVisibilityKeeper::DeleteContentNodeAndJoinTextNodesAroundIt(
2296 aHTMLEditor, aAtomicContent, aCaretPoint);
2297 if (NS_WARN_IF(NS_FAILED(rv))) {
2298 NS_WARNING(
2299 "WhiteSpaceVisibilityKeeper::DeleteContentNodeAndJoinTextNodesAroundIt("
2300 ") failed");
2301 return EditActionHandled(rv);
2304 EditorDOMPoint newCaretPosition =
2305 EditorBase::GetStartPoint(*aHTMLEditor.SelectionRefPtr());
2306 if (!newCaretPosition.IsSet()) {
2307 NS_WARNING("There was no selection range");
2308 return EditActionHandled(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
2311 rv = aHTMLEditor.InsertBRElementIfHardLineIsEmptyAndEndsWithBlockBoundary(
2312 newCaretPosition);
2313 NS_WARNING_ASSERTION(
2314 NS_SUCCEEDED(rv),
2315 "HTMLEditor::InsertBRElementIfHardLineIsEmptyAndEndsWithBlockBoundary() "
2316 "failed");
2317 return EditActionHandled(rv);
2320 bool HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
2321 PrepareToDeleteAtOtherBlockBoundary(
2322 const HTMLEditor& aHTMLEditor,
2323 nsIEditor::EDirection aDirectionAndAmount, Element& aOtherBlockElement,
2324 const EditorDOMPoint& aCaretPoint,
2325 const WSRunScanner& aWSRunScannerAtCaret) {
2326 MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable());
2327 MOZ_ASSERT(aCaretPoint.IsSetAndValid());
2329 mMode = Mode::JoinOtherBlock;
2331 // Make sure it's not a table element. If so, cancel the operation
2332 // (translation: users cannot backspace or delete across table cells)
2333 if (HTMLEditUtils::IsAnyTableElement(&aOtherBlockElement)) {
2334 return false;
2337 // First find the adjacent node in the block
2338 if (aDirectionAndAmount == nsIEditor::ePrevious) {
2339 mLeafContentInOtherBlock =
2340 aHTMLEditor.GetLastEditableLeaf(aOtherBlockElement);
2341 mLeftContent = mLeafContentInOtherBlock;
2342 mRightContent = aCaretPoint.GetContainerAsContent();
2343 } else {
2344 mLeafContentInOtherBlock =
2345 aHTMLEditor.GetFirstEditableLeaf(aOtherBlockElement);
2346 mLeftContent = aCaretPoint.GetContainerAsContent();
2347 mRightContent = mLeafContentInOtherBlock;
2350 // Next to a block. See if we are between the block and a `<br>`.
2351 // If so, we really want to delete the `<br>`. Else join content at
2352 // selection to the block.
2353 WSScanResult scanFromCaretResult =
2354 aDirectionAndAmount == nsIEditor::eNext
2355 ? aWSRunScannerAtCaret.ScanPreviousVisibleNodeOrBlockBoundaryFrom(
2356 aCaretPoint)
2357 : aWSRunScannerAtCaret.ScanNextVisibleNodeOrBlockBoundaryFrom(
2358 aCaretPoint);
2359 // If we found a `<br>` element, we need to delete it instead of joining the
2360 // contents.
2361 if (scanFromCaretResult.ReachedBRElement()) {
2362 mBRElement = scanFromCaretResult.BRElementPtr();
2363 mMode = Mode::DeleteBRElement;
2364 return true;
2367 return mLeftContent && mRightContent;
2370 nsresult HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
2371 ComputeRangesToDeleteBRElement(AutoRangeArray& aRangesToDelete) const {
2372 MOZ_ASSERT(mBRElement);
2373 // XXX Why don't we scan invisible leading white-spaces which follows the
2374 // `<br>` element?
2375 nsresult rv = aRangesToDelete.SelectNode(*mBRElement);
2376 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "AutoRangeArray::SelectNode() failed");
2377 return rv;
2380 EditActionResult
2381 HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::DeleteBRElement(
2382 HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount,
2383 const EditorDOMPoint& aCaretPoint) {
2384 MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable());
2385 MOZ_ASSERT(aCaretPoint.IsSetAndValid());
2386 MOZ_ASSERT(mBRElement);
2388 // If we found a `<br>` element, we should delete it instead of joining the
2389 // contents.
2390 nsresult rv =
2391 aHTMLEditor.DeleteNodeWithTransaction(MOZ_KnownLive(*mBRElement));
2392 if (NS_WARN_IF(aHTMLEditor.Destroyed())) {
2393 return EditActionResult(NS_ERROR_EDITOR_DESTROYED);
2395 if (NS_FAILED(rv)) {
2396 NS_WARNING("HTMLEditor::DeleteNodeWithTransaction() failed");
2397 return EditActionResult(rv);
2400 if (mLeftContent && mRightContent &&
2401 HTMLEditor::NodesInDifferentTableElements(*mLeftContent,
2402 *mRightContent)) {
2403 return EditActionHandled();
2406 // Put selection at edge of block and we are done.
2407 if (NS_WARN_IF(!mLeafContentInOtherBlock)) {
2408 // XXX This must be odd case. The other block can be empty.
2409 return EditActionHandled(NS_ERROR_FAILURE);
2411 EditorDOMPoint newCaretPosition = aHTMLEditor.GetGoodCaretPointFor(
2412 *mLeafContentInOtherBlock, aDirectionAndAmount);
2413 if (!newCaretPosition.IsSet()) {
2414 NS_WARNING("HTMLEditor::GetGoodCaretPointFor() failed");
2415 return EditActionHandled(NS_ERROR_FAILURE);
2417 rv = aHTMLEditor.CollapseSelectionTo(newCaretPosition);
2418 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
2419 return EditActionHandled(NS_ERROR_EDITOR_DESTROYED);
2421 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
2422 "HTMLEditor::CollapseSelectionTo() failed, but ignored");
2423 return EditActionHandled();
2426 nsresult HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
2427 ComputeRangesToDeleteAtOtherBlockBoundary(
2428 const HTMLEditor& aHTMLEditor,
2429 nsIEditor::EDirection aDirectionAndAmount,
2430 const EditorDOMPoint& aCaretPoint,
2431 AutoRangeArray& aRangesToDelete) const {
2432 MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable());
2433 MOZ_ASSERT(aCaretPoint.IsSetAndValid());
2434 MOZ_ASSERT(mLeftContent);
2435 MOZ_ASSERT(mRightContent);
2437 if (HTMLEditor::NodesInDifferentTableElements(*mLeftContent,
2438 *mRightContent)) {
2439 if (!mDeleteRangesHandlerConst.CanFallbackToDeleteRangesWithTransaction(
2440 aRangesToDelete)) {
2441 nsresult rv = aRangesToDelete.Collapse(aCaretPoint);
2442 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
2443 "AutoRangeArray::Collapse() failed");
2444 return rv;
2446 nsresult rv = mDeleteRangesHandlerConst
2447 .FallbackToComputeRangesToDeleteRangesWithTransaction(
2448 aHTMLEditor, aRangesToDelete);
2449 NS_WARNING_ASSERTION(
2450 NS_SUCCEEDED(rv),
2451 "AutoDeleteRangesHandler::"
2452 "FallbackToComputeRangesToDeleteRangesWithTransaction() failed");
2453 return rv;
2456 AutoInclusiveAncestorBlockElementsJoiner joiner(*mLeftContent,
2457 *mRightContent);
2458 Result<bool, nsresult> canJoinThem = joiner.Prepare(aHTMLEditor);
2459 if (canJoinThem.isErr()) {
2460 NS_WARNING("AutoInclusiveAncestorBlockElementsJoiner::Prepare() failed");
2461 return canJoinThem.unwrapErr();
2463 if (canJoinThem.inspect() && joiner.CanJoinBlocks() &&
2464 !joiner.ShouldDeleteLeafContentInstead()) {
2465 nsresult rv =
2466 joiner.ComputeRangesToDelete(aHTMLEditor, aCaretPoint, aRangesToDelete);
2467 NS_WARNING_ASSERTION(
2468 NS_SUCCEEDED(rv),
2469 "AutoInclusiveAncestorBlockElementsJoiner::ComputeRangesToDelete() "
2470 "failed");
2471 return rv;
2474 // If AutoInclusiveAncestorBlockElementsJoiner didn't handle it and it's not
2475 // canceled, user may want to modify the start leaf node or the last leaf
2476 // node of the block.
2477 if (mLeafContentInOtherBlock == aCaretPoint.GetContainer()) {
2478 return NS_OK;
2481 AutoHideSelectionChanges hideSelectionChanges(aHTMLEditor.SelectionRefPtr());
2483 // If it's ignored, it didn't modify the DOM tree. In this case, user must
2484 // want to delete nearest leaf node in the other block element.
2485 // TODO: We need to consider this before calling ComputeRangesToDelete() for
2486 // computing the deleting range.
2487 EditorRawDOMPoint newCaretPoint =
2488 aDirectionAndAmount == nsIEditor::ePrevious
2489 ? EditorRawDOMPoint::AtEndOf(*mLeafContentInOtherBlock)
2490 : EditorRawDOMPoint(mLeafContentInOtherBlock, 0);
2491 // If new caret position is same as current caret position, we can do
2492 // nothing anymore.
2493 if (aRangesToDelete.IsCollapsed() &&
2494 aRangesToDelete.FocusRef() == newCaretPoint.ToRawRangeBoundary()) {
2495 return NS_OK;
2497 // TODO: Stop modifying the `Selection` for computing the targer ranges.
2498 nsresult rv = aHTMLEditor.CollapseSelectionTo(newCaretPoint);
2499 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
2500 "HTMLEditor::CollapseSelectionTo() failed");
2501 if (NS_SUCCEEDED(rv)) {
2502 aRangesToDelete.Initialize(*aHTMLEditor.SelectionRefPtr());
2503 AutoDeleteRangesHandler anotherHandler(mDeleteRangesHandlerConst);
2504 rv = anotherHandler.ComputeRangesToDelete(aHTMLEditor, aDirectionAndAmount,
2505 aRangesToDelete);
2506 NS_WARNING_ASSERTION(
2507 NS_SUCCEEDED(rv),
2508 "Recursive AutoDeleteRangesHandler::ComputeRangesToDelete() failed");
2510 // Restore selection.
2511 nsresult rvCollapsingSelectionTo =
2512 aHTMLEditor.CollapseSelectionTo(aCaretPoint);
2513 NS_WARNING_ASSERTION(
2514 NS_SUCCEEDED(rvCollapsingSelectionTo),
2515 "HTMLEditor::CollapseSelectionTo() failed to restore caret position");
2516 return NS_SUCCEEDED(rv) && NS_SUCCEEDED(rvCollapsingSelectionTo)
2517 ? NS_OK
2518 : NS_ERROR_FAILURE;
2521 EditActionResult HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
2522 HandleDeleteAtOtherBlockBoundary(HTMLEditor& aHTMLEditor,
2523 nsIEditor::EDirection aDirectionAndAmount,
2524 nsIEditor::EStripWrappers aStripWrappers,
2525 const EditorDOMPoint& aCaretPoint,
2526 AutoRangeArray& aRangesToDelete) {
2527 MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable());
2528 MOZ_ASSERT(aCaretPoint.IsSetAndValid());
2529 MOZ_ASSERT(mDeleteRangesHandler);
2530 MOZ_ASSERT(mLeftContent);
2531 MOZ_ASSERT(mRightContent);
2533 if (HTMLEditor::NodesInDifferentTableElements(*mLeftContent,
2534 *mRightContent)) {
2535 // If we have not deleted `<br>` element and are not called recursively,
2536 // we should call `DeleteRangesWithTransaction()` here.
2537 if (!mDeleteRangesHandler->CanFallbackToDeleteRangesWithTransaction(
2538 aRangesToDelete)) {
2539 return EditActionIgnored();
2541 EditActionResult result =
2542 mDeleteRangesHandler->FallbackToDeleteRangesWithTransaction(
2543 aHTMLEditor, aRangesToDelete);
2544 NS_WARNING_ASSERTION(
2545 result.Succeeded(),
2546 "AutoDeleteRangesHandler::FallbackToDeleteRangesWithTransaction() "
2547 "failed to delete leaf content in the block");
2548 return result;
2551 // Else we are joining content to block
2552 AutoInclusiveAncestorBlockElementsJoiner joiner(*mLeftContent,
2553 *mRightContent);
2554 Result<bool, nsresult> canJoinThem = joiner.Prepare(aHTMLEditor);
2555 if (canJoinThem.isErr()) {
2556 NS_WARNING("AutoInclusiveAncestorBlockElementsJoiner::Prepare() failed");
2557 return EditActionResult(canJoinThem.unwrapErr());
2560 if (!canJoinThem.inspect()) {
2561 nsresult rv = aHTMLEditor.CollapseSelectionTo(aCaretPoint);
2562 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
2563 return EditActionResult(NS_ERROR_EDITOR_DESTROYED);
2565 NS_WARNING_ASSERTION(
2566 NS_SUCCEEDED(rv),
2567 "HTMLEditor::CollapseSelectionTo() failed, but ignored");
2568 return EditActionCanceled();
2571 EditActionResult result(NS_OK);
2572 EditorDOMPoint pointToPutCaret(aCaretPoint);
2573 if (joiner.CanJoinBlocks()) {
2575 AutoTrackDOMPoint tracker(aHTMLEditor.RangeUpdaterRef(),
2576 &pointToPutCaret);
2577 result |= joiner.Run(aHTMLEditor);
2578 if (result.Failed()) {
2579 NS_WARNING("AutoInclusiveAncestorBlockElementsJoiner::Run() failed");
2580 return result;
2582 #ifdef DEBUG
2583 if (joiner.ShouldDeleteLeafContentInstead()) {
2584 NS_ASSERTION(
2585 result.Ignored(),
2586 "Assumed `AutoInclusiveAncestorBlockElementsJoiner::Run()` "
2587 "retruning ignored, but returned not ignored");
2588 } else {
2589 NS_ASSERTION(
2590 !result.Ignored(),
2591 "Assumed `AutoInclusiveAncestorBlockElementsJoiner::Run()` "
2592 "retruning handled, but returned ignored");
2594 #endif // #ifdef DEBUG
2597 // If AutoInclusiveAncestorBlockElementsJoiner didn't handle it and it's not
2598 // canceled, user may want to modify the start leaf node or the last leaf
2599 // node of the block.
2600 if (result.Ignored() &&
2601 mLeafContentInOtherBlock != aCaretPoint.GetContainer()) {
2602 // If it's ignored, it didn't modify the DOM tree. In this case, user
2603 // must want to delete nearest leaf node in the other block element.
2604 // TODO: We need to consider this before calling Run() for computing the
2605 // deleting range.
2606 EditorRawDOMPoint newCaretPoint =
2607 aDirectionAndAmount == nsIEditor::ePrevious
2608 ? EditorRawDOMPoint::AtEndOf(*mLeafContentInOtherBlock)
2609 : EditorRawDOMPoint(mLeafContentInOtherBlock, 0);
2610 // If new caret position is same as current caret position, we can do
2611 // nothing anymore.
2612 if (aRangesToDelete.IsCollapsed() &&
2613 aRangesToDelete.FocusRef() == newCaretPoint.ToRawRangeBoundary()) {
2614 return EditActionCanceled();
2616 nsresult rv = aHTMLEditor.CollapseSelectionTo(newCaretPoint);
2617 if (NS_FAILED(rv)) {
2618 NS_WARNING("HTMLEditor::CollapseSelectionTo() failed");
2619 return result.SetResult(rv);
2621 AutoRangeArray rangesToDelete(*aHTMLEditor.SelectionRefPtr());
2622 AutoDeleteRangesHandler anotherHandler(mDeleteRangesHandler);
2623 result = anotherHandler.Run(aHTMLEditor, aDirectionAndAmount,
2624 aStripWrappers, rangesToDelete);
2625 NS_WARNING_ASSERTION(result.Succeeded(),
2626 "Recursive AutoDeleteRangesHandler::Run() failed");
2627 return result;
2629 } else {
2630 result.MarkAsHandled();
2633 // Otherwise, we must have deleted the selection as user expected.
2634 nsresult rv = aHTMLEditor.CollapseSelectionTo(pointToPutCaret);
2635 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
2636 return result.SetResult(NS_ERROR_EDITOR_DESTROYED);
2638 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
2639 "HTMLEditor::CollapseSelectionTo() failed, but ignored");
2640 return result;
2643 bool HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
2644 PrepareToDeleteAtCurrentBlockBoundary(
2645 const HTMLEditor& aHTMLEditor,
2646 nsIEditor::EDirection aDirectionAndAmount,
2647 Element& aCurrentBlockElement, const EditorDOMPoint& aCaretPoint) {
2648 MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable());
2650 // At edge of our block. Look beside it and see if we can join to an
2651 // adjacent block
2652 mMode = Mode::JoinCurrentBlock;
2654 // Don't break the basic structure of the HTML document.
2655 if (aCurrentBlockElement.IsAnyOfHTMLElements(nsGkAtoms::html, nsGkAtoms::head,
2656 nsGkAtoms::body)) {
2657 return false;
2660 // Make sure it's not a table element. If so, cancel the operation
2661 // (translation: users cannot backspace or delete across table cells)
2662 if (HTMLEditUtils::IsAnyTableElement(&aCurrentBlockElement)) {
2663 return false;
2666 if (aDirectionAndAmount == nsIEditor::ePrevious) {
2667 mLeftContent =
2668 aHTMLEditor.GetPreviousEditableHTMLNode(aCurrentBlockElement);
2669 mRightContent = aCaretPoint.GetContainerAsContent();
2670 } else {
2671 mRightContent = aHTMLEditor.GetNextEditableHTMLNode(aCurrentBlockElement);
2672 mLeftContent = aCaretPoint.GetContainerAsContent();
2675 // Nothing to join
2676 if (!mLeftContent || !mRightContent) {
2677 return false;
2680 // Don't cross table boundaries.
2681 return !HTMLEditor::NodesInDifferentTableElements(*mLeftContent,
2682 *mRightContent);
2685 nsresult HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
2686 ComputeRangesToDeleteAtCurrentBlockBoundary(
2687 const HTMLEditor& aHTMLEditor, const EditorDOMPoint& aCaretPoint,
2688 AutoRangeArray& aRangesToDelete) const {
2689 MOZ_ASSERT(mLeftContent);
2690 MOZ_ASSERT(mRightContent);
2692 AutoInclusiveAncestorBlockElementsJoiner joiner(*mLeftContent,
2693 *mRightContent);
2694 Result<bool, nsresult> canJoinThem = joiner.Prepare(aHTMLEditor);
2695 if (canJoinThem.isErr()) {
2696 NS_WARNING("AutoInclusiveAncestorBlockElementsJoiner::Prepare() failed");
2697 return canJoinThem.unwrapErr();
2699 if (canJoinThem.inspect()) {
2700 nsresult rv =
2701 joiner.ComputeRangesToDelete(aHTMLEditor, aCaretPoint, aRangesToDelete);
2702 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
2703 "AutoInclusiveAncestorBlockElementsJoiner::"
2704 "ComputeRangesToDelete() failed");
2705 return rv;
2708 // In this case, nothing will be deleted so that the affected range should
2709 // be collapsed.
2710 nsresult rv = aRangesToDelete.Collapse(aCaretPoint);
2711 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "AutoRangeArray::Collapse() failed");
2712 return rv;
2715 EditActionResult HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
2716 HandleDeleteAtCurrentBlockBoundary(HTMLEditor& aHTMLEditor,
2717 const EditorDOMPoint& aCaretPoint) {
2718 MOZ_ASSERT(mLeftContent);
2719 MOZ_ASSERT(mRightContent);
2721 AutoInclusiveAncestorBlockElementsJoiner joiner(*mLeftContent,
2722 *mRightContent);
2723 Result<bool, nsresult> canJoinThem = joiner.Prepare(aHTMLEditor);
2724 if (canJoinThem.isErr()) {
2725 NS_WARNING("AutoInclusiveAncestorBlockElementsJoiner::Prepare() failed");
2726 return EditActionResult(canJoinThem.unwrapErr());
2729 if (!canJoinThem.inspect()) {
2730 nsresult rv = aHTMLEditor.CollapseSelectionTo(aCaretPoint);
2731 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
2732 return EditActionResult(NS_ERROR_EDITOR_DESTROYED);
2734 NS_WARNING_ASSERTION(
2735 NS_SUCCEEDED(rv),
2736 "HTMLEditor::CollapseSelectionTo() failed, but ignored");
2737 return EditActionCanceled();
2740 EditActionResult result(NS_OK);
2741 EditorDOMPoint pointToPutCaret(aCaretPoint);
2742 if (joiner.CanJoinBlocks()) {
2743 AutoTrackDOMPoint tracker(aHTMLEditor.RangeUpdaterRef(), &pointToPutCaret);
2744 result |= joiner.Run(aHTMLEditor);
2745 if (result.Failed()) {
2746 NS_WARNING("AutoInclusiveAncestorBlockElementsJoiner::Run() failed");
2747 return result;
2749 #ifdef DEBUG
2750 if (joiner.ShouldDeleteLeafContentInstead()) {
2751 NS_ASSERTION(result.Ignored(),
2752 "Assumed `AutoInclusiveAncestorBlockElementsJoiner::Run()` "
2753 "retruning ignored, but returned not ignored");
2754 } else {
2755 NS_ASSERTION(!result.Ignored(),
2756 "Assumed `AutoInclusiveAncestorBlockElementsJoiner::Run()` "
2757 "retruning handled, but returned ignored");
2759 #endif // #ifdef DEBUG
2761 // This should claim that trying to join the block means that
2762 // this handles the action because the caller shouldn't do anything
2763 // anymore in this case.
2764 result.MarkAsHandled();
2766 nsresult rv = aHTMLEditor.CollapseSelectionTo(pointToPutCaret);
2767 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
2768 return result.SetResult(NS_ERROR_EDITOR_DESTROYED);
2770 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
2771 "HTMLEditor::CollapseSelectionTo() failed, but ignored");
2772 return result;
2775 nsresult
2776 HTMLEditor::AutoDeleteRangesHandler::ComputeRangesToDeleteNonCollapsedRanges(
2777 const HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount,
2778 AutoRangeArray& aRangesToDelete,
2779 AutoDeleteRangesHandler::SelectionWasCollapsed aSelectionWasCollapsed)
2780 const {
2781 MOZ_ASSERT(!aRangesToDelete.IsCollapsed());
2783 if (NS_WARN_IF(!aRangesToDelete.FirstRangeRef()->StartRef().IsSet()) ||
2784 NS_WARN_IF(!aRangesToDelete.FirstRangeRef()->EndRef().IsSet())) {
2785 return NS_ERROR_FAILURE;
2788 if (aRangesToDelete.Ranges().Length() == 1) {
2789 nsFrameSelection* frameSelection =
2790 aHTMLEditor.SelectionRefPtr()->GetFrameSelection();
2791 if (NS_WARN_IF(!frameSelection)) {
2792 return NS_ERROR_FAILURE;
2794 if (!ExtendRangeToIncludeInvisibleNodes(aHTMLEditor, frameSelection,
2795 aRangesToDelete.FirstRangeRef())) {
2796 NS_WARNING(
2797 "AutoDeleteRangesHandler::ExtendRangeToIncludeInvisibleNodes() "
2798 "failed");
2799 return NS_ERROR_FAILURE;
2803 if (!aHTMLEditor.IsPlaintextEditor()) {
2804 EditorDOMRange firstRange(aRangesToDelete.FirstRangeRef());
2805 EditorDOMRange extendedRange =
2806 WSRunScanner::GetRangeContainingInvisibleWhiteSpacesAtRangeBoundaries(
2807 aHTMLEditor, EditorDOMRange(aRangesToDelete.FirstRangeRef()));
2808 if (firstRange != extendedRange) {
2809 nsresult rv = aRangesToDelete.FirstRangeRef()->SetStartAndEnd(
2810 extendedRange.StartRef().ToRawRangeBoundary(),
2811 extendedRange.EndRef().ToRawRangeBoundary());
2812 if (NS_FAILED(rv)) {
2813 NS_WARNING("nsRange::SetStartAndEnd() failed");
2814 return NS_ERROR_FAILURE;
2819 if (aRangesToDelete.FirstRangeRef()->GetStartContainer() ==
2820 aRangesToDelete.FirstRangeRef()->GetEndContainer()) {
2821 if (!aRangesToDelete.FirstRangeRef()->Collapsed()) {
2822 nsresult rv = ComputeRangesToDeleteRangesWithTransaction(
2823 aHTMLEditor, aDirectionAndAmount, aRangesToDelete);
2824 NS_WARNING_ASSERTION(
2825 NS_SUCCEEDED(rv),
2826 "AutoDeleteRangesHandler::ComputeRangesToDeleteRangesWithTransaction("
2827 ") failed");
2828 return rv;
2830 // `DeleteUnnecessaryNodesAndCollapseSelection()` may delete parent
2831 // elements, but it does not affect computing target ranges. Therefore,
2832 // we don't need to touch aRangesToDelete in this case.
2833 return NS_OK;
2836 Element* startCiteNode = aHTMLEditor.GetMostAncestorMailCiteElement(
2837 *aRangesToDelete.FirstRangeRef()->GetStartContainer());
2838 Element* endCiteNode = aHTMLEditor.GetMostAncestorMailCiteElement(
2839 *aRangesToDelete.FirstRangeRef()->GetEndContainer());
2841 if (startCiteNode && !endCiteNode) {
2842 aDirectionAndAmount = nsIEditor::eNext;
2843 } else if (!startCiteNode && endCiteNode) {
2844 aDirectionAndAmount = nsIEditor::ePrevious;
2847 AutoBlockElementsJoiner joiner(*this);
2848 if (!joiner.PrepareToDeleteNonCollapsedRanges(aHTMLEditor, aRangesToDelete)) {
2849 return NS_ERROR_FAILURE;
2851 nsresult rv =
2852 joiner.ComputeRangesToDelete(aHTMLEditor, aDirectionAndAmount,
2853 aRangesToDelete, aSelectionWasCollapsed);
2854 NS_WARNING_ASSERTION(
2855 NS_SUCCEEDED(rv),
2856 "AutoBlockElementsJoiner::ComputeRangesToDelete() failed");
2857 return rv;
2860 EditActionResult
2861 HTMLEditor::AutoDeleteRangesHandler::HandleDeleteNonCollapsedRanges(
2862 HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount,
2863 nsIEditor::EStripWrappers aStripWrappers, AutoRangeArray& aRangesToDelete,
2864 SelectionWasCollapsed aSelectionWasCollapsed) {
2865 MOZ_ASSERT(aHTMLEditor.IsTopLevelEditSubActionDataAvailable());
2866 MOZ_ASSERT(!aRangesToDelete.IsCollapsed());
2868 if (NS_WARN_IF(!aRangesToDelete.FirstRangeRef()->StartRef().IsSet()) ||
2869 NS_WARN_IF(!aRangesToDelete.FirstRangeRef()->EndRef().IsSet())) {
2870 return EditActionResult(NS_ERROR_FAILURE);
2873 // Else we have a non-collapsed selection. First adjust the selection.
2874 // XXX Why do we extend selection only when there is only one range?
2875 if (aRangesToDelete.Ranges().Length() == 1) {
2876 nsFrameSelection* frameSelection =
2877 aHTMLEditor.SelectionRefPtr()->GetFrameSelection();
2878 if (NS_WARN_IF(!frameSelection)) {
2879 return EditActionResult(NS_ERROR_FAILURE);
2881 if (!ExtendRangeToIncludeInvisibleNodes(aHTMLEditor, frameSelection,
2882 aRangesToDelete.FirstRangeRef())) {
2883 NS_WARNING(
2884 "AutoDeleteRangesHandler::ExtendRangeToIncludeInvisibleNodes() "
2885 "failed");
2886 return EditActionResult(NS_ERROR_FAILURE);
2890 // Remember that we did a ranged delete for the benefit of AfterEditInner().
2891 aHTMLEditor.TopLevelEditSubActionDataRef().mDidDeleteNonCollapsedRange = true;
2893 // Figure out if the endpoints are in nodes that can be merged. Adjust
2894 // surrounding white-space in preparation to delete selection.
2895 if (!aHTMLEditor.IsPlaintextEditor()) {
2896 AutoTransactionsConserveSelection dontChangeMySelection(aHTMLEditor);
2897 AutoTrackDOMRange firstRangeTracker(aHTMLEditor.RangeUpdaterRef(),
2898 &aRangesToDelete.FirstRangeRef());
2899 nsresult rv = WhiteSpaceVisibilityKeeper::PrepareToDeleteRange(
2900 aHTMLEditor, EditorDOMRange(aRangesToDelete.FirstRangeRef()));
2901 if (NS_FAILED(rv)) {
2902 NS_WARNING(
2903 "WhiteSpaceVisibilityKeeper::PrepareToDeleteRange() "
2904 "failed");
2905 return EditActionResult(rv);
2907 if (NS_WARN_IF(
2908 !aRangesToDelete.FirstRangeRef()->StartRef().IsSetAndValid()) ||
2909 NS_WARN_IF(
2910 !aRangesToDelete.FirstRangeRef()->EndRef().IsSetAndValid())) {
2911 NS_WARNING(
2912 "WhiteSpaceVisibilityKeeper::PrepareToDeleteRange() made the firstr "
2913 "range invalid");
2914 return EditActionHandled(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
2918 // XXX This is odd. We do we simply use `DeleteRangesWithTransaction()`
2919 // only when **first** range is in same container?
2920 if (aRangesToDelete.FirstRangeRef()->GetStartContainer() ==
2921 aRangesToDelete.FirstRangeRef()->GetEndContainer()) {
2922 // Because of previous DOM tree changes, the range may be collapsed.
2923 // If we've already removed all contents in the range, we shouldn't
2924 // delete anything around the caret.
2925 if (!aRangesToDelete.FirstRangeRef()->Collapsed()) {
2926 AutoTrackDOMRange firstRangeTracker(aHTMLEditor.RangeUpdaterRef(),
2927 &aRangesToDelete.FirstRangeRef());
2928 nsresult rv = aHTMLEditor.DeleteRangesWithTransaction(
2929 aDirectionAndAmount, aStripWrappers, aRangesToDelete);
2930 if (NS_FAILED(rv)) {
2931 NS_WARNING("EditorBase::DeleteRangesWithTransaction() failed");
2932 return EditActionHandled(rv);
2935 // However, even if the range is removed, we may need to clean up the
2936 // containers which become empty.
2937 nsresult rv = DeleteUnnecessaryNodesAndCollapseSelection(
2938 aHTMLEditor, aDirectionAndAmount,
2939 EditorDOMPoint(aRangesToDelete.FirstRangeRef()->StartRef()),
2940 EditorDOMPoint(aRangesToDelete.FirstRangeRef()->EndRef()));
2941 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
2942 "AutoDeleteRangesHandler::"
2943 "DeleteUnnecessaryNodesAndCollapseSelection() failed");
2944 return EditActionHandled(rv);
2947 if (NS_WARN_IF(
2948 !aRangesToDelete.FirstRangeRef()->GetStartContainer()->IsContent()) ||
2949 NS_WARN_IF(
2950 !aRangesToDelete.FirstRangeRef()->GetEndContainer()->IsContent())) {
2951 return EditActionHandled(NS_ERROR_FAILURE); // XXX "handled"?
2954 // Figure out mailcite ancestors
2955 RefPtr<Element> startCiteNode = aHTMLEditor.GetMostAncestorMailCiteElement(
2956 *aRangesToDelete.FirstRangeRef()->GetStartContainer());
2957 RefPtr<Element> endCiteNode = aHTMLEditor.GetMostAncestorMailCiteElement(
2958 *aRangesToDelete.FirstRangeRef()->GetEndContainer());
2960 // If we only have a mailcite at one of the two endpoints, set the
2961 // directionality of the deletion so that the selection will end up
2962 // outside the mailcite.
2963 if (startCiteNode && !endCiteNode) {
2964 aDirectionAndAmount = nsIEditor::eNext;
2965 } else if (!startCiteNode && endCiteNode) {
2966 aDirectionAndAmount = nsIEditor::ePrevious;
2969 AutoBlockElementsJoiner joiner(*this);
2970 if (!joiner.PrepareToDeleteNonCollapsedRanges(aHTMLEditor, aRangesToDelete)) {
2971 return EditActionHandled(NS_ERROR_FAILURE);
2973 EditActionResult result =
2974 joiner.Run(aHTMLEditor, aDirectionAndAmount, aStripWrappers,
2975 aRangesToDelete, aSelectionWasCollapsed);
2976 NS_WARNING_ASSERTION(result.Succeeded(),
2977 "AutoBlockElementsJoiner::Run() failed");
2978 return result;
2981 bool HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
2982 PrepareToDeleteNonCollapsedRanges(const HTMLEditor& aHTMLEditor,
2983 const AutoRangeArray& aRangesToDelete) {
2984 MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable());
2985 MOZ_ASSERT(!aRangesToDelete.IsCollapsed());
2987 mLeftContent = HTMLEditUtils::GetInclusiveAncestorBlockElement(
2988 *aRangesToDelete.FirstRangeRef()->GetStartContainer()->AsContent());
2989 mRightContent = HTMLEditUtils::GetInclusiveAncestorBlockElement(
2990 *aRangesToDelete.FirstRangeRef()->GetEndContainer()->AsContent());
2991 if (NS_WARN_IF(!mLeftContent) || NS_WARN_IF(!mRightContent)) {
2992 return false;
2994 if (mLeftContent == mRightContent) {
2995 mMode = Mode::DeleteContentInRanges;
2996 return true;
2999 // If left block and right block are adjuscent siblings and they are same
3000 // type of elements, we can merge them after deleting the selected contents.
3001 // MOOSE: this could conceivably screw up a table.. fix me.
3002 if (mLeftContent->GetParentNode() == mRightContent->GetParentNode() &&
3003 HTMLEditUtils::CanContentsBeJoined(
3004 *mLeftContent, *mRightContent,
3005 aHTMLEditor.IsCSSEnabled() ? StyleDifference::CompareIfSpanElements
3006 : StyleDifference::Ignore) &&
3007 // XXX What's special about these three types of block?
3008 (mLeftContent->IsHTMLElement(nsGkAtoms::p) ||
3009 HTMLEditUtils::IsListItem(mLeftContent) ||
3010 HTMLEditUtils::IsHeader(*mLeftContent))) {
3011 mMode = Mode::JoinBlocksInSameParent;
3012 return true;
3015 mMode = Mode::DeleteNonCollapsedRanges;
3016 return true;
3019 nsresult HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
3020 ComputeRangesToDeleteContentInRanges(
3021 const HTMLEditor& aHTMLEditor,
3022 nsIEditor::EDirection aDirectionAndAmount,
3023 AutoRangeArray& aRangesToDelete) const {
3024 MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable());
3025 MOZ_ASSERT(!aRangesToDelete.IsCollapsed());
3026 MOZ_ASSERT(mMode == Mode::DeleteContentInRanges);
3027 MOZ_ASSERT(mLeftContent);
3028 MOZ_ASSERT(mLeftContent->IsElement());
3029 MOZ_ASSERT(aRangesToDelete.FirstRangeRef()
3030 ->GetStartContainer()
3031 ->IsInclusiveDescendantOf(mLeftContent));
3032 MOZ_ASSERT(mRightContent);
3033 MOZ_ASSERT(mRightContent->IsElement());
3034 MOZ_ASSERT(aRangesToDelete.FirstRangeRef()
3035 ->GetEndContainer()
3036 ->IsInclusiveDescendantOf(mRightContent));
3038 nsresult rv =
3039 mDeleteRangesHandlerConst.ComputeRangesToDeleteRangesWithTransaction(
3040 aHTMLEditor, aDirectionAndAmount, aRangesToDelete);
3041 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
3042 "AutoDeleteRangesHandler::"
3043 "ComputeRangesToDeleteRangesWithTransaction() failed");
3044 return rv;
3047 EditActionResult HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
3048 DeleteContentInRanges(HTMLEditor& aHTMLEditor,
3049 nsIEditor::EDirection aDirectionAndAmount,
3050 nsIEditor::EStripWrappers aStripWrappers,
3051 AutoRangeArray& aRangesToDelete) {
3052 MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable());
3053 MOZ_ASSERT(!aRangesToDelete.IsCollapsed());
3054 MOZ_ASSERT(mMode == Mode::DeleteContentInRanges);
3055 MOZ_ASSERT(mDeleteRangesHandler);
3056 MOZ_ASSERT(mLeftContent);
3057 MOZ_ASSERT(mLeftContent->IsElement());
3058 MOZ_ASSERT(aRangesToDelete.FirstRangeRef()
3059 ->GetStartContainer()
3060 ->IsInclusiveDescendantOf(mLeftContent));
3061 MOZ_ASSERT(mRightContent);
3062 MOZ_ASSERT(mRightContent->IsElement());
3063 MOZ_ASSERT(aRangesToDelete.FirstRangeRef()
3064 ->GetEndContainer()
3065 ->IsInclusiveDescendantOf(mRightContent));
3067 // XXX This is also odd. We do we simply use
3068 // `DeleteRangesWithTransaction()` only when **first** range is in
3069 // same block?
3070 MOZ_ASSERT(mLeftContent == mRightContent);
3072 AutoTrackDOMRange firstRangeTracker(aHTMLEditor.RangeUpdaterRef(),
3073 &aRangesToDelete.FirstRangeRef());
3074 nsresult rv = aHTMLEditor.DeleteRangesWithTransaction(
3075 aDirectionAndAmount, aStripWrappers, aRangesToDelete);
3076 if (rv == NS_ERROR_EDITOR_DESTROYED) {
3077 NS_WARNING(
3078 "EditorBase::DeleteRangesWithTransaction() caused destroying the "
3079 "editor");
3080 return EditActionHandled(NS_ERROR_EDITOR_DESTROYED);
3082 NS_WARNING_ASSERTION(
3083 NS_SUCCEEDED(rv),
3084 "EditorBase::DeleteRangesWithTransaction() failed, but ignored");
3086 nsresult rv =
3087 mDeleteRangesHandler->DeleteUnnecessaryNodesAndCollapseSelection(
3088 aHTMLEditor, aDirectionAndAmount,
3089 EditorDOMPoint(aRangesToDelete.FirstRangeRef()->StartRef()),
3090 EditorDOMPoint(aRangesToDelete.FirstRangeRef()->EndRef()));
3091 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
3092 "AutoDeleteRangesHandler::"
3093 "DeleteUnnecessaryNodesAndCollapseSelection() failed");
3094 return EditActionHandled(rv);
3097 nsresult HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
3098 ComputeRangesToJoinBlockElementsInSameParent(
3099 const HTMLEditor& aHTMLEditor,
3100 nsIEditor::EDirection aDirectionAndAmount,
3101 AutoRangeArray& aRangesToDelete) const {
3102 MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable());
3103 MOZ_ASSERT(!aRangesToDelete.IsCollapsed());
3104 MOZ_ASSERT(mMode == Mode::JoinBlocksInSameParent);
3105 MOZ_ASSERT(mLeftContent);
3106 MOZ_ASSERT(mLeftContent->IsElement());
3107 MOZ_ASSERT(aRangesToDelete.FirstRangeRef()
3108 ->GetStartContainer()
3109 ->IsInclusiveDescendantOf(mLeftContent));
3110 MOZ_ASSERT(mRightContent);
3111 MOZ_ASSERT(mRightContent->IsElement());
3112 MOZ_ASSERT(aRangesToDelete.FirstRangeRef()
3113 ->GetEndContainer()
3114 ->IsInclusiveDescendantOf(mRightContent));
3115 MOZ_ASSERT(mLeftContent->GetParentNode() == mRightContent->GetParentNode());
3117 nsresult rv =
3118 mDeleteRangesHandlerConst.ComputeRangesToDeleteRangesWithTransaction(
3119 aHTMLEditor, aDirectionAndAmount, aRangesToDelete);
3120 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
3121 "AutoDeleteRangesHandler::"
3122 "ComputeRangesToDeleteRangesWithTransaction() failed");
3123 return rv;
3126 EditActionResult HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
3127 JoinBlockElementsInSameParent(HTMLEditor& aHTMLEditor,
3128 nsIEditor::EDirection aDirectionAndAmount,
3129 nsIEditor::EStripWrappers aStripWrappers,
3130 AutoRangeArray& aRangesToDelete) {
3131 MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable());
3132 MOZ_ASSERT(!aRangesToDelete.IsCollapsed());
3133 MOZ_ASSERT(mMode == Mode::JoinBlocksInSameParent);
3134 MOZ_ASSERT(mLeftContent);
3135 MOZ_ASSERT(mLeftContent->IsElement());
3136 MOZ_ASSERT(aRangesToDelete.FirstRangeRef()
3137 ->GetStartContainer()
3138 ->IsInclusiveDescendantOf(mLeftContent));
3139 MOZ_ASSERT(mRightContent);
3140 MOZ_ASSERT(mRightContent->IsElement());
3141 MOZ_ASSERT(aRangesToDelete.FirstRangeRef()
3142 ->GetEndContainer()
3143 ->IsInclusiveDescendantOf(mRightContent));
3144 MOZ_ASSERT(mLeftContent->GetParentNode() == mRightContent->GetParentNode());
3146 nsresult rv = aHTMLEditor.DeleteRangesWithTransaction(
3147 aDirectionAndAmount, aStripWrappers, aRangesToDelete);
3148 if (NS_FAILED(rv)) {
3149 NS_WARNING("EditorBase::DeleteRangesWithTransaction() failed");
3150 return EditActionHandled(rv);
3153 Result<EditorDOMPoint, nsresult> atFirstChildOfTheLastRightNodeOrError =
3154 JoinNodesDeepWithTransaction(aHTMLEditor, MOZ_KnownLive(*mLeftContent),
3155 MOZ_KnownLive(*mRightContent));
3156 if (atFirstChildOfTheLastRightNodeOrError.isErr()) {
3157 NS_WARNING("HTMLEditor::JoinNodesDeepWithTransaction() failed");
3158 return EditActionHandled(atFirstChildOfTheLastRightNodeOrError.unwrapErr());
3160 MOZ_ASSERT(atFirstChildOfTheLastRightNodeOrError.inspect().IsSet());
3162 rv = aHTMLEditor.CollapseSelectionTo(
3163 atFirstChildOfTheLastRightNodeOrError.inspect());
3164 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
3165 "HTMLEditor::CollapseSelectionTo() failed");
3166 return EditActionHandled(rv);
3169 Result<bool, nsresult>
3170 HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
3171 ComputeRangesToDeleteNodesEntirelyInRangeButKeepTableStructure(
3172 const HTMLEditor& aHTMLEditor, nsRange& aRange,
3173 AutoDeleteRangesHandler::SelectionWasCollapsed aSelectionWasCollapsed)
3174 const {
3175 MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable());
3177 AutoTArray<OwningNonNull<nsIContent>, 10> arrayOfTopChildren;
3178 DOMSubtreeIterator iter;
3179 nsresult rv = iter.Init(aRange);
3180 if (NS_FAILED(rv)) {
3181 NS_WARNING("DOMSubtreeIterator::Init() failed");
3182 return Err(rv);
3184 iter.AppendAllNodesToArray(arrayOfTopChildren);
3185 return NeedsToJoinNodesAfterDeleteNodesEntirelyInRangeButKeepTableStructure(
3186 aHTMLEditor, arrayOfTopChildren, aSelectionWasCollapsed);
3189 Result<bool, nsresult> HTMLEditor::AutoDeleteRangesHandler::
3190 AutoBlockElementsJoiner::DeleteNodesEntirelyInRangeButKeepTableStructure(
3191 HTMLEditor& aHTMLEditor, nsRange& aRange,
3192 AutoDeleteRangesHandler::SelectionWasCollapsed aSelectionWasCollapsed) {
3193 MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable());
3195 // Build a list of direct child nodes in the range
3196 AutoTArray<OwningNonNull<nsIContent>, 10> arrayOfTopChildren;
3197 DOMSubtreeIterator iter;
3198 nsresult rv = iter.Init(aRange);
3199 if (NS_FAILED(rv)) {
3200 NS_WARNING("DOMSubtreeIterator::Init() failed");
3201 return Err(rv);
3203 iter.AppendAllNodesToArray(arrayOfTopChildren);
3205 // Now that we have the list, delete non-table elements
3206 bool needsToJoinLater =
3207 NeedsToJoinNodesAfterDeleteNodesEntirelyInRangeButKeepTableStructure(
3208 aHTMLEditor, arrayOfTopChildren, aSelectionWasCollapsed);
3209 for (auto& content : arrayOfTopChildren) {
3210 // XXX After here, the child contents in the array may have been moved
3211 // to somewhere or removed. We should handle it.
3213 // MOZ_KnownLive because 'arrayOfTopChildren' is guaranteed to
3214 // keep it alive.
3216 // Even with https://bugzilla.mozilla.org/show_bug.cgi?id=1620312 fixed
3217 // this might need to stay, because 'arrayOfTopChildren' is not const,
3218 // so it's not obvious how to prove via static analysis that it won't
3219 // change and release us.
3220 nsresult rv =
3221 DeleteContentButKeepTableStructure(aHTMLEditor, MOZ_KnownLive(content));
3222 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
3223 return Err(NS_ERROR_EDITOR_DESTROYED);
3225 NS_WARNING_ASSERTION(
3226 NS_SUCCEEDED(rv),
3227 "AutoBlockElementsJoiner::DeleteContentButKeepTableStructure() failed, "
3228 "but ignored");
3230 return needsToJoinLater;
3233 bool HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
3234 NeedsToJoinNodesAfterDeleteNodesEntirelyInRangeButKeepTableStructure(
3235 const HTMLEditor& aHTMLEditor,
3236 const nsTArray<OwningNonNull<nsIContent>>& aArrayOfContents,
3237 AutoDeleteRangesHandler::SelectionWasCollapsed aSelectionWasCollapsed)
3238 const {
3239 // If original selection was collapsed, we need always to join the nodes.
3240 // XXX Why?
3241 if (aSelectionWasCollapsed ==
3242 AutoDeleteRangesHandler::SelectionWasCollapsed::No) {
3243 return true;
3245 // If something visible is deleted, no need to join. Visible means
3246 // all nodes except non-visible textnodes and breaks.
3247 if (aArrayOfContents.IsEmpty()) {
3248 return true;
3250 for (const OwningNonNull<nsIContent>& content : aArrayOfContents) {
3251 if (content->IsText()) {
3252 if (aHTMLEditor.IsInVisibleTextFrames(*content->AsText())) {
3253 return false;
3255 continue;
3257 // XXX If it's an element node, we should check whether it has visible
3258 // frames or not.
3259 if (!content->IsElement() ||
3260 aHTMLEditor.IsEmptyNode(*content->AsElement())) {
3261 continue;
3263 if (!content->IsHTMLElement(nsGkAtoms::br) ||
3264 aHTMLEditor.IsVisibleBRElement(content)) {
3265 return false;
3268 return true;
3271 nsresult HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
3272 DeleteTextAtStartAndEndOfRange(HTMLEditor& aHTMLEditor, nsRange& aRange) {
3273 EditorDOMPoint rangeStart(aRange.StartRef());
3274 EditorDOMPoint rangeEnd(aRange.EndRef());
3275 if (rangeStart.IsInTextNode() && !rangeStart.IsEndOfContainer()) {
3276 // Delete to last character
3277 OwningNonNull<Text> textNode = *rangeStart.GetContainerAsText();
3278 nsresult rv = aHTMLEditor.DeleteTextWithTransaction(
3279 textNode, rangeStart.Offset(),
3280 rangeStart.GetContainer()->Length() - rangeStart.Offset());
3281 if (NS_WARN_IF(aHTMLEditor.Destroyed())) {
3282 return NS_ERROR_EDITOR_DESTROYED;
3284 if (NS_FAILED(rv)) {
3285 NS_WARNING("HTMLEditor::DeleteTextWithTransaction() failed");
3286 return rv;
3289 if (rangeEnd.IsInTextNode() && !rangeEnd.IsStartOfContainer()) {
3290 // Delete to first character
3291 OwningNonNull<Text> textNode = *rangeEnd.GetContainerAsText();
3292 nsresult rv =
3293 aHTMLEditor.DeleteTextWithTransaction(textNode, 0, rangeEnd.Offset());
3294 if (NS_WARN_IF(aHTMLEditor.Destroyed())) {
3295 return NS_ERROR_EDITOR_DESTROYED;
3297 if (NS_FAILED(rv)) {
3298 NS_WARNING("HTMLEditor::DeleteTextWithTransaction() failed");
3299 return rv;
3302 return NS_OK;
3305 nsresult HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
3306 ComputeRangesToDeleteNonCollapsedRanges(
3307 const HTMLEditor& aHTMLEditor,
3308 nsIEditor::EDirection aDirectionAndAmount,
3309 AutoRangeArray& aRangesToDelete,
3310 AutoDeleteRangesHandler::SelectionWasCollapsed aSelectionWasCollapsed)
3311 const {
3312 MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable());
3313 MOZ_ASSERT(!aRangesToDelete.IsCollapsed());
3314 MOZ_ASSERT(mLeftContent);
3315 MOZ_ASSERT(mLeftContent->IsElement());
3316 MOZ_ASSERT(aRangesToDelete.FirstRangeRef()
3317 ->GetStartContainer()
3318 ->IsInclusiveDescendantOf(mLeftContent));
3319 MOZ_ASSERT(mRightContent);
3320 MOZ_ASSERT(mRightContent->IsElement());
3321 MOZ_ASSERT(aRangesToDelete.FirstRangeRef()
3322 ->GetEndContainer()
3323 ->IsInclusiveDescendantOf(mRightContent));
3325 for (OwningNonNull<nsRange>& range : aRangesToDelete.Ranges()) {
3326 Result<bool, nsresult> result =
3327 ComputeRangesToDeleteNodesEntirelyInRangeButKeepTableStructure(
3328 aHTMLEditor, range, aSelectionWasCollapsed);
3329 if (result.isErr()) {
3330 NS_WARNING(
3331 "AutoBlockElementsJoiner::"
3332 "ComputeRangesToDeleteNodesEntirelyInRangeButKeepTableStructure() "
3333 "failed");
3334 return result.unwrapErr();
3336 if (!result.unwrap()) {
3337 return NS_OK;
3341 AutoInclusiveAncestorBlockElementsJoiner joiner(*mLeftContent,
3342 *mRightContent);
3343 Result<bool, nsresult> canJoinThem = joiner.Prepare(aHTMLEditor);
3344 if (canJoinThem.isErr()) {
3345 NS_WARNING("AutoInclusiveAncestorBlockElementsJoiner::Prepare() failed");
3346 return canJoinThem.unwrapErr();
3349 if (!canJoinThem.unwrap()) {
3350 return NS_SUCCESS_DOM_NO_OPERATION;
3353 if (!joiner.CanJoinBlocks()) {
3354 return NS_OK;
3357 nsresult rv = joiner.ComputeRangesToDelete(aHTMLEditor, EditorDOMPoint(),
3358 aRangesToDelete);
3359 NS_WARNING_ASSERTION(
3360 NS_SUCCEEDED(rv),
3361 "AutoInclusiveAncestorBlockElementsJoiner::ComputeRangesToDelete() "
3362 "failed");
3363 return rv;
3366 EditActionResult HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
3367 HandleDeleteNonCollapsedRanges(
3368 HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount,
3369 nsIEditor::EStripWrappers aStripWrappers,
3370 AutoRangeArray& aRangesToDelete,
3371 AutoDeleteRangesHandler::SelectionWasCollapsed aSelectionWasCollapsed) {
3372 MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable());
3373 MOZ_ASSERT(!aRangesToDelete.IsCollapsed());
3374 MOZ_ASSERT(mDeleteRangesHandler);
3375 MOZ_ASSERT(mLeftContent);
3376 MOZ_ASSERT(mLeftContent->IsElement());
3377 MOZ_ASSERT(aRangesToDelete.FirstRangeRef()
3378 ->GetStartContainer()
3379 ->IsInclusiveDescendantOf(mLeftContent));
3380 MOZ_ASSERT(mRightContent);
3381 MOZ_ASSERT(mRightContent->IsElement());
3382 MOZ_ASSERT(aRangesToDelete.FirstRangeRef()
3383 ->GetEndContainer()
3384 ->IsInclusiveDescendantOf(mRightContent));
3386 // Otherwise, delete every nodes in all ranges, then, clean up something.
3387 EditActionResult result(NS_OK);
3388 while (true) {
3389 AutoTrackDOMRange firstRangeTracker(aHTMLEditor.RangeUpdaterRef(),
3390 &aRangesToDelete.FirstRangeRef());
3392 bool joinInclusiveAncestorBlockElements = true;
3393 for (auto& range : aRangesToDelete.Ranges()) {
3394 Result<bool, nsresult> deleteResult =
3395 DeleteNodesEntirelyInRangeButKeepTableStructure(
3396 aHTMLEditor, MOZ_KnownLive(range), aSelectionWasCollapsed);
3397 if (deleteResult.isErr()) {
3398 NS_WARNING(
3399 "AutoBlockElementsJoiner::"
3400 "DeleteNodesEntirelyInRangeButKeepTableStructure() failed");
3401 return EditActionResult(deleteResult.unwrapErr());
3403 // XXX Completely odd. Why don't we join blocks around each range?
3404 joinInclusiveAncestorBlockElements &= deleteResult.unwrap();
3407 // Check endpoints for possible text deletion. We can assume that if
3408 // text node is found, we can delete to end or to begining as
3409 // appropriate, since the case where both sel endpoints in same text
3410 // node was already handled (we wouldn't be here)
3411 nsresult rv = DeleteTextAtStartAndEndOfRange(
3412 aHTMLEditor, MOZ_KnownLive(aRangesToDelete.FirstRangeRef()));
3413 if (NS_FAILED(rv)) {
3414 NS_WARNING(
3415 "AutoBlockElementsJoiner::DeleteTextAtStartAndEndOfRange() failed");
3416 return EditActionHandled(rv);
3419 if (!joinInclusiveAncestorBlockElements) {
3420 break;
3423 AutoInclusiveAncestorBlockElementsJoiner joiner(*mLeftContent,
3424 *mRightContent);
3425 Result<bool, nsresult> canJoinThem = joiner.Prepare(aHTMLEditor);
3426 if (canJoinThem.isErr()) {
3427 NS_WARNING("AutoInclusiveAncestorBlockElementsJoiner::Prepare() failed");
3428 return EditActionResult(canJoinThem.unwrapErr());
3431 // If we're joining blocks: if deleting forward the selection should
3432 // be collapsed to the end of the selection, if deleting backward the
3433 // selection should be collapsed to the beginning of the selection.
3434 // But if we're not joining then the selection should collapse to the
3435 // beginning of the selection if we'redeleting forward, because the
3436 // end of the selection will still be in the next block. And same
3437 // thing for deleting backwards (selection should collapse to the end,
3438 // because the beginning will still be in the first block). See Bug
3439 // 507936.
3440 if (aDirectionAndAmount == nsIEditor::eNext) {
3441 aDirectionAndAmount = nsIEditor::ePrevious;
3442 } else {
3443 aDirectionAndAmount = nsIEditor::eNext;
3446 if (!canJoinThem.inspect()) {
3447 result.MarkAsCanceled();
3448 break;
3451 if (!joiner.CanJoinBlocks()) {
3452 break;
3455 result |= joiner.Run(aHTMLEditor);
3456 if (result.Failed()) {
3457 NS_WARNING("AutoInclusiveAncestorBlockElementsJoiner::Run() failed");
3458 return result;
3460 #ifdef DEBUG
3461 if (joiner.ShouldDeleteLeafContentInstead()) {
3462 NS_ASSERTION(result.Ignored(),
3463 "Assumed `AutoInclusiveAncestorBlockElementsJoiner::Run()` "
3464 "retruning ignored, but returned not ignored");
3465 } else {
3466 NS_ASSERTION(!result.Ignored(),
3467 "Assumed `AutoInclusiveAncestorBlockElementsJoiner::Run()` "
3468 "retruning handled, but returned ignored");
3470 #endif // #ifdef DEBUG
3471 break;
3474 nsresult rv =
3475 mDeleteRangesHandler->DeleteUnnecessaryNodesAndCollapseSelection(
3476 aHTMLEditor, aDirectionAndAmount,
3477 EditorDOMPoint(aRangesToDelete.FirstRangeRef()->StartRef()),
3478 EditorDOMPoint(aRangesToDelete.FirstRangeRef()->EndRef()));
3479 if (NS_FAILED(rv)) {
3480 NS_WARNING(
3481 "AutoDeleteRangesHandler::DeleteUnnecessaryNodesAndCollapseSelection() "
3482 "failed");
3483 return EditActionResult(rv);
3486 return result.MarkAsHandled();
3489 nsresult
3490 HTMLEditor::AutoDeleteRangesHandler::DeleteUnnecessaryNodesAndCollapseSelection(
3491 HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount,
3492 const EditorDOMPoint& aSelectionStartPoint,
3493 const EditorDOMPoint& aSelectionEndPoint) {
3494 MOZ_ASSERT(aHTMLEditor.IsTopLevelEditSubActionDataAvailable());
3496 EditorDOMPoint atCaret(aSelectionStartPoint);
3497 EditorDOMPoint selectionEndPoint(aSelectionEndPoint);
3499 // If we're handling D&D, this is called to delete dragging item from the
3500 // tree. In this case, we should remove parent blocks if it becomes empty.
3501 if (aHTMLEditor.GetEditAction() == EditAction::eDrop ||
3502 aHTMLEditor.GetEditAction() == EditAction::eDeleteByDrag) {
3503 MOZ_ASSERT((atCaret.GetContainer() == selectionEndPoint.GetContainer() &&
3504 atCaret.Offset() == selectionEndPoint.Offset()) ||
3505 (atCaret.GetContainer()->GetNextSibling() ==
3506 selectionEndPoint.GetContainer() &&
3507 atCaret.IsEndOfContainer() &&
3508 selectionEndPoint.IsStartOfContainer()));
3510 AutoTrackDOMPoint startTracker(aHTMLEditor.RangeUpdaterRef(), &atCaret);
3511 AutoTrackDOMPoint endTracker(aHTMLEditor.RangeUpdaterRef(),
3512 &selectionEndPoint);
3514 nsresult rv =
3515 DeleteParentBlocksWithTransactionIfEmpty(aHTMLEditor, atCaret);
3516 if (NS_FAILED(rv)) {
3517 NS_WARNING(
3518 "HTMLEditor::DeleteParentBlocksWithTransactionIfEmpty() failed");
3519 return rv;
3521 aHTMLEditor.TopLevelEditSubActionDataRef().mDidDeleteEmptyParentBlocks =
3522 rv == NS_OK;
3524 // If we removed parent blocks, Selection should be collapsed at where
3525 // the most ancestor empty block has been.
3526 if (aHTMLEditor.TopLevelEditSubActionDataRef()
3527 .mDidDeleteEmptyParentBlocks) {
3528 nsresult rv = aHTMLEditor.CollapseSelectionTo(atCaret);
3529 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
3530 "HTMLEditor::CollapseSelectionTo() failed");
3531 return rv;
3535 if (NS_WARN_IF(!atCaret.IsInContentNode()) ||
3536 NS_WARN_IF(!selectionEndPoint.IsInContentNode())) {
3537 return NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE;
3540 // We might have left only collapsed white-space in the start/end nodes
3542 AutoTrackDOMPoint startTracker(aHTMLEditor.RangeUpdaterRef(), &atCaret);
3543 AutoTrackDOMPoint endTracker(aHTMLEditor.RangeUpdaterRef(),
3544 &selectionEndPoint);
3546 nsresult rv = DeleteNodeIfInvisibleAndEditableTextNode(
3547 aHTMLEditor, MOZ_KnownLive(*atCaret.ContainerAsContent()));
3548 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
3549 return NS_ERROR_EDITOR_DESTROYED;
3551 NS_WARNING_ASSERTION(
3552 NS_SUCCEEDED(rv),
3553 "AutoDeleteRangesHandler::DeleteNodeIfInvisibleAndEditableTextNode() "
3554 "failed to remove start node, but ignored");
3555 rv = DeleteNodeIfInvisibleAndEditableTextNode(
3556 aHTMLEditor, MOZ_KnownLive(*selectionEndPoint.ContainerAsContent()));
3557 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
3558 return NS_ERROR_EDITOR_DESTROYED;
3560 NS_WARNING_ASSERTION(
3561 NS_SUCCEEDED(rv),
3562 "AutoDeleteRangesHandler::DeleteNodeIfInvisibleAndEditableTextNode() "
3563 "failed to remove end node, but ignored");
3566 nsresult rv = aHTMLEditor.CollapseSelectionTo(
3567 aDirectionAndAmount == nsIEditor::ePrevious ? selectionEndPoint
3568 : atCaret);
3569 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
3570 "HTMLEditor::CollapseSelectionTo() failed");
3571 return rv;
3574 nsresult
3575 HTMLEditor::AutoDeleteRangesHandler::DeleteNodeIfInvisibleAndEditableTextNode(
3576 HTMLEditor& aHTMLEditor, nsIContent& aContent) {
3577 MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable());
3579 Text* text = aContent.GetAsText();
3580 if (!text) {
3581 return NS_OK;
3584 if (aHTMLEditor.IsVisibleTextNode(*text) ||
3585 !HTMLEditUtils::IsSimplyEditableNode(*text)) {
3586 return NS_OK;
3589 nsresult rv = aHTMLEditor.DeleteNodeWithTransaction(aContent);
3590 if (NS_WARN_IF(aHTMLEditor.Destroyed())) {
3591 return NS_ERROR_EDITOR_DESTROYED;
3593 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
3594 "HTMLEditor::DeleteNodeWithTransaction() failed");
3595 return rv;
3598 nsresult
3599 HTMLEditor::AutoDeleteRangesHandler::DeleteParentBlocksWithTransactionIfEmpty(
3600 HTMLEditor& aHTMLEditor, const EditorDOMPoint& aPoint) {
3601 MOZ_ASSERT(aPoint.IsSet());
3602 MOZ_ASSERT(aHTMLEditor.mPlaceholderBatch);
3604 // First, check there is visible contents before the point in current block.
3605 WSRunScanner wsScannerForPoint(aHTMLEditor, aPoint);
3606 if (!wsScannerForPoint.StartsFromCurrentBlockBoundary()) {
3607 // If there is visible node before the point, we shouldn't remove the
3608 // parent block.
3609 return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND;
3611 if (NS_WARN_IF(!wsScannerForPoint.GetStartReasonContent()) ||
3612 NS_WARN_IF(!wsScannerForPoint.GetStartReasonContent()->GetParentNode())) {
3613 return NS_ERROR_FAILURE;
3615 if (wsScannerForPoint.GetEditingHost() ==
3616 wsScannerForPoint.GetStartReasonContent()) {
3617 // If we reach editing host, there is no parent blocks which can be removed.
3618 return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND;
3620 if (HTMLEditUtils::IsTableCellOrCaption(
3621 *wsScannerForPoint.GetStartReasonContent())) {
3622 // If we reach a <td>, <th> or <caption>, we shouldn't remove it even
3623 // becomes empty because removing such element changes the structure of
3624 // the <table>.
3625 return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND;
3628 // Next, check there is visible contents after the point in current block.
3629 WSScanResult forwardScanFromPointResult =
3630 wsScannerForPoint.ScanNextVisibleNodeOrBlockBoundaryFrom(aPoint);
3631 if (forwardScanFromPointResult.Failed()) {
3632 NS_WARNING("WSRunScanner::ScanNextVisibleNodeOrBlockBoundaryFrom() failed");
3633 return NS_ERROR_FAILURE;
3635 if (forwardScanFromPointResult.ReachedBRElement()) {
3636 // XXX In my understanding, this is odd. The end reason may not be
3637 // same as the reached <br> element because the equality is
3638 // guaranteed only when ReachedCurrentBlockBoundary() returns true.
3639 // However, looks like that this code assumes that
3640 // GetEndReasonContent() returns the (or a) <br> element.
3641 NS_ASSERTION(wsScannerForPoint.GetEndReasonContent() ==
3642 forwardScanFromPointResult.BRElementPtr(),
3643 "End reason is not the reached <br> element");
3644 // If the <br> element is visible, we shouldn't remove the parent block.
3645 if (aHTMLEditor.IsVisibleBRElement(
3646 wsScannerForPoint.GetEndReasonContent())) {
3647 return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND;
3649 if (wsScannerForPoint.GetEndReasonContent()->GetNextSibling()) {
3650 WSScanResult scanResult =
3651 WSRunScanner::ScanNextVisibleNodeOrBlockBoundary(
3652 aHTMLEditor, EditorRawDOMPoint::After(
3653 *wsScannerForPoint.GetEndReasonContent()));
3654 if (scanResult.Failed()) {
3655 NS_WARNING("WSRunScanner::ScanNextVisibleNodeOrBlockBoundary() failed");
3656 return NS_ERROR_FAILURE;
3658 if (!scanResult.ReachedCurrentBlockBoundary()) {
3659 // If we couldn't reach the block's end after the invisible <br>,
3660 // that means that there is visible content.
3661 return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND;
3664 } else if (!forwardScanFromPointResult.ReachedCurrentBlockBoundary()) {
3665 // If we couldn't reach the block's end, the block has visible content.
3666 return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND;
3669 // Delete the parent block.
3670 EditorDOMPoint nextPoint(
3671 wsScannerForPoint.GetStartReasonContent()->GetParentNode(), 0);
3672 nsresult rv = aHTMLEditor.DeleteNodeWithTransaction(
3673 MOZ_KnownLive(*wsScannerForPoint.GetStartReasonContent()));
3674 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
3675 return NS_ERROR_EDITOR_DESTROYED;
3677 if (NS_FAILED(rv)) {
3678 NS_WARNING("HTMLEditor::DeleteNodeWithTransaction() failed");
3679 return rv;
3681 // If we reach editing host, return NS_OK.
3682 if (nextPoint.GetContainer() == wsScannerForPoint.GetEditingHost()) {
3683 return NS_OK;
3686 // Otherwise, we need to check whether we're still in empty block or not.
3688 // If we have mutation event listeners, the next point is now outside of
3689 // editing host or editing hos has been changed.
3690 if (aHTMLEditor.MayHaveMutationEventListeners(
3691 NS_EVENT_BITS_MUTATION_NODEREMOVED |
3692 NS_EVENT_BITS_MUTATION_NODEREMOVEDFROMDOCUMENT |
3693 NS_EVENT_BITS_MUTATION_SUBTREEMODIFIED)) {
3694 Element* editingHost = aHTMLEditor.GetActiveEditingHost();
3695 if (NS_WARN_IF(!editingHost) ||
3696 NS_WARN_IF(editingHost != wsScannerForPoint.GetEditingHost())) {
3697 return NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE;
3699 if (NS_WARN_IF(!EditorUtils::IsDescendantOf(*nextPoint.GetContainer(),
3700 *editingHost))) {
3701 return NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE;
3705 rv = DeleteParentBlocksWithTransactionIfEmpty(aHTMLEditor, nextPoint);
3706 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
3707 "AutoDeleteRangesHandler::"
3708 "DeleteParentBlocksWithTransactionIfEmpty() failed");
3709 return rv;
3712 nsresult
3713 HTMLEditor::AutoDeleteRangesHandler::ComputeRangesToDeleteRangesWithTransaction(
3714 const HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount,
3715 AutoRangeArray& aRangesToDelete) const {
3716 MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable());
3717 MOZ_ASSERT(!aRangesToDelete.Ranges().IsEmpty());
3719 EditorBase::HowToHandleCollapsedRange howToHandleCollapsedRange =
3720 EditorBase::HowToHandleCollapsedRangeFor(aDirectionAndAmount);
3721 if (NS_WARN_IF(aRangesToDelete.IsCollapsed() &&
3722 howToHandleCollapsedRange ==
3723 EditorBase::HowToHandleCollapsedRange::Ignore)) {
3724 return NS_ERROR_FAILURE;
3727 auto extendRangeToSelectCharacterForward =
3728 [](nsRange& aRange, const EditorRawDOMPointInText& aCaretPoint) -> void {
3729 const nsTextFragment& textFragment =
3730 aCaretPoint.ContainerAsText()->TextFragment();
3731 if (!textFragment.GetLength()) {
3732 return;
3734 if (textFragment.IsHighSurrogateFollowedByLowSurrogateAt(
3735 aCaretPoint.Offset())) {
3736 DebugOnly<nsresult> rvIgnored = aRange.SetStartAndEnd(
3737 aCaretPoint.ContainerAsText(), aCaretPoint.Offset(),
3738 aCaretPoint.ContainerAsText(), aCaretPoint.Offset() + 2);
3739 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
3740 "nsRange::SetStartAndEnd() failed");
3741 return;
3743 DebugOnly<nsresult> rvIgnored = aRange.SetStartAndEnd(
3744 aCaretPoint.ContainerAsText(), aCaretPoint.Offset(),
3745 aCaretPoint.ContainerAsText(), aCaretPoint.Offset() + 1);
3746 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
3747 "nsRange::SetStartAndEnd() failed");
3749 auto extendRangeToSelectCharacterBackward =
3750 [](nsRange& aRange, const EditorRawDOMPointInText& aCaretPoint) -> void {
3751 if (aCaretPoint.IsStartOfContainer()) {
3752 return;
3754 const nsTextFragment& textFragment =
3755 aCaretPoint.ContainerAsText()->TextFragment();
3756 if (!textFragment.GetLength()) {
3757 return;
3759 if (textFragment.IsLowSurrogateFollowingHighSurrogateAt(
3760 aCaretPoint.Offset() - 1)) {
3761 DebugOnly<nsresult> rvIgnored = aRange.SetStartAndEnd(
3762 aCaretPoint.ContainerAsText(), aCaretPoint.Offset() - 2,
3763 aCaretPoint.ContainerAsText(), aCaretPoint.Offset());
3764 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
3765 "nsRange::SetStartAndEnd() failed");
3766 return;
3768 DebugOnly<nsresult> rvIgnored = aRange.SetStartAndEnd(
3769 aCaretPoint.ContainerAsText(), aCaretPoint.Offset() - 1,
3770 aCaretPoint.ContainerAsText(), aCaretPoint.Offset());
3771 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
3772 "nsRange::SetStartAndEnd() failed");
3775 for (OwningNonNull<nsRange>& range : aRangesToDelete.Ranges()) {
3776 // If it's not collapsed, `DeleteRangeTransaction::Create()` will be called
3777 // with it and `DeleteRangeTransaction` won't modify the range.
3778 if (!range->Collapsed()) {
3779 continue;
3782 if (howToHandleCollapsedRange ==
3783 EditorBase::HowToHandleCollapsedRange::Ignore) {
3784 continue;
3787 // In the other cases, `EditorBase::CreateTransactionForCollapsedRange()`
3788 // will handle the collapsed range.
3789 EditorRawDOMPoint caretPoint(range->StartRef());
3790 if (howToHandleCollapsedRange ==
3791 EditorBase::HowToHandleCollapsedRange::ExtendBackward &&
3792 caretPoint.IsStartOfContainer()) {
3793 nsIContent* previousEditableContent =
3794 aHTMLEditor.GetPreviousEditableNode(*caretPoint.GetContainer());
3795 if (!previousEditableContent) {
3796 continue;
3798 if (!previousEditableContent->IsText()) {
3799 IgnoredErrorResult ignoredError;
3800 range->SelectNode(*previousEditableContent, ignoredError);
3801 NS_WARNING_ASSERTION(!ignoredError.Failed(),
3802 "nsRange::SelectNode() failed");
3803 continue;
3806 extendRangeToSelectCharacterBackward(
3807 range,
3808 EditorRawDOMPointInText::AtEndOf(*previousEditableContent->AsText()));
3809 continue;
3812 if (howToHandleCollapsedRange ==
3813 EditorBase::HowToHandleCollapsedRange::ExtendForward &&
3814 caretPoint.IsEndOfContainer()) {
3815 nsIContent* nextEditableContent =
3816 aHTMLEditor.GetNextEditableNode(*caretPoint.GetContainer());
3817 if (!nextEditableContent) {
3818 continue;
3821 if (!nextEditableContent->IsText()) {
3822 IgnoredErrorResult ignoredError;
3823 range->SelectNode(*nextEditableContent, ignoredError);
3824 NS_WARNING_ASSERTION(!ignoredError.Failed(),
3825 "nsRange::SelectNode() failed");
3826 continue;
3829 extendRangeToSelectCharacterForward(
3830 range, EditorRawDOMPointInText(nextEditableContent->AsText(), 0));
3831 continue;
3834 if (caretPoint.IsInTextNode()) {
3835 if (howToHandleCollapsedRange ==
3836 EditorBase::HowToHandleCollapsedRange::ExtendBackward) {
3837 extendRangeToSelectCharacterBackward(
3838 range, EditorRawDOMPointInText(caretPoint.ContainerAsText(),
3839 caretPoint.Offset()));
3840 continue;
3842 extendRangeToSelectCharacterForward(
3843 range, EditorRawDOMPointInText(caretPoint.ContainerAsText(),
3844 caretPoint.Offset()));
3845 continue;
3848 nsIContent* editableContent =
3849 howToHandleCollapsedRange ==
3850 EditorBase::HowToHandleCollapsedRange::ExtendBackward
3851 ? aHTMLEditor.GetPreviousEditableNode(caretPoint)
3852 : aHTMLEditor.GetNextEditableNode(caretPoint);
3853 if (!editableContent) {
3854 continue;
3856 while (editableContent && editableContent->IsCharacterData() &&
3857 !editableContent->Length()) {
3858 editableContent =
3859 howToHandleCollapsedRange ==
3860 EditorBase::HowToHandleCollapsedRange::ExtendBackward
3861 ? aHTMLEditor.GetPreviousEditableNode(*editableContent)
3862 : aHTMLEditor.GetNextEditableNode(*editableContent);
3864 if (!editableContent) {
3865 continue;
3868 if (!editableContent->IsText()) {
3869 IgnoredErrorResult ignoredError;
3870 range->SelectNode(*editableContent, ignoredError);
3871 NS_WARNING_ASSERTION(!ignoredError.Failed(),
3872 "nsRange::SelectNode() failed");
3873 continue;
3876 if (howToHandleCollapsedRange ==
3877 EditorBase::HowToHandleCollapsedRange::ExtendBackward) {
3878 extendRangeToSelectCharacterBackward(
3879 range, EditorRawDOMPointInText::AtEndOf(*editableContent->AsText()));
3880 continue;
3882 extendRangeToSelectCharacterForward(
3883 range, EditorRawDOMPointInText(editableContent->AsText(), 0));
3886 return NS_OK;
3889 template <typename EditorDOMPointType>
3890 nsresult HTMLEditor::DeleteTextAndTextNodesWithTransaction(
3891 const EditorDOMPointType& aStartPoint, const EditorDOMPointType& aEndPoint,
3892 TreatEmptyTextNodes aTreatEmptyTextNodes) {
3893 if (NS_WARN_IF(!aStartPoint.IsSet()) || NS_WARN_IF(!aEndPoint.IsSet())) {
3894 return NS_ERROR_INVALID_ARG;
3897 // MOOSE: this routine needs to be modified to preserve the integrity of the
3898 // wsFragment info.
3900 if (aStartPoint == aEndPoint) {
3901 // Nothing to delete
3902 return NS_OK;
3905 RefPtr<Element> editingHost = GetActiveEditingHost();
3906 auto deleteEmptyContentNodeWithTransaction =
3907 [this, &aTreatEmptyTextNodes, &editingHost](nsIContent& aContent)
3908 MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION -> nsresult {
3909 OwningNonNull<nsIContent> nodeToRemove = aContent;
3910 if (aTreatEmptyTextNodes ==
3911 TreatEmptyTextNodes::RemoveAllEmptyInlineAncestors) {
3912 Element* emptyParentElementToRemove =
3913 HTMLEditUtils::GetMostDistantAnscestorEditableEmptyInlineElement(
3914 nodeToRemove, editingHost);
3915 if (emptyParentElementToRemove) {
3916 nodeToRemove = *emptyParentElementToRemove;
3919 nsresult rv = DeleteNodeWithTransaction(nodeToRemove);
3920 if (NS_WARN_IF(Destroyed())) {
3921 return NS_ERROR_EDITOR_DESTROYED;
3923 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
3924 "HTMLEditor::DeleteNodeWithTransaction() failed");
3925 return rv;
3928 if (aStartPoint.GetContainer() == aEndPoint.GetContainer() &&
3929 aStartPoint.IsInTextNode()) {
3930 if (aTreatEmptyTextNodes !=
3931 TreatEmptyTextNodes::KeepIfContainerOfRangeBoundaries &&
3932 aStartPoint.IsStartOfContainer() && aEndPoint.IsEndOfContainer()) {
3933 nsresult rv = deleteEmptyContentNodeWithTransaction(
3934 MOZ_KnownLive(*aStartPoint.ContainerAsText()));
3935 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
3936 "deleteEmptyContentNodeWithTransaction() failed");
3937 return rv;
3939 RefPtr<Text> textNode = aStartPoint.ContainerAsText();
3940 nsresult rv =
3941 DeleteTextWithTransaction(*textNode, aStartPoint.Offset(),
3942 aEndPoint.Offset() - aStartPoint.Offset());
3943 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
3944 "HTMLEditor::DeleteTextWithTransaction() failed");
3945 return rv;
3948 RefPtr<nsRange> range =
3949 nsRange::Create(aStartPoint.ToRawRangeBoundary(),
3950 aEndPoint.ToRawRangeBoundary(), IgnoreErrors());
3951 if (!range) {
3952 NS_WARNING("nsRange::Create() failed");
3953 return NS_ERROR_FAILURE;
3956 // Collect editable text nodes in the given range.
3957 AutoTArray<OwningNonNull<Text>, 16> arrayOfTextNodes;
3958 DOMIterator iter;
3959 if (NS_FAILED(iter.Init(*range))) {
3960 return NS_OK; // Nothing to delete in the range.
3962 iter.AppendNodesToArray(
3963 +[](nsINode& aNode, void*) {
3964 MOZ_ASSERT(aNode.IsText());
3965 return HTMLEditUtils::IsSimplyEditableNode(aNode);
3967 arrayOfTextNodes);
3968 for (OwningNonNull<Text>& textNode : arrayOfTextNodes) {
3969 if (textNode == aStartPoint.GetContainer()) {
3970 if (aStartPoint.IsEndOfContainer()) {
3971 continue;
3973 if (aStartPoint.IsStartOfContainer() &&
3974 aTreatEmptyTextNodes !=
3975 TreatEmptyTextNodes::KeepIfContainerOfRangeBoundaries) {
3976 nsresult rv = deleteEmptyContentNodeWithTransaction(
3977 MOZ_KnownLive(*aStartPoint.ContainerAsText()));
3978 if (NS_FAILED(rv)) {
3979 NS_WARNING("deleteEmptyContentNodeWithTransaction() failed");
3980 return rv;
3982 continue;
3984 nsresult rv = DeleteTextWithTransaction(
3985 MOZ_KnownLive(textNode), aStartPoint.Offset(),
3986 textNode->Length() - aStartPoint.Offset());
3987 if (NS_WARN_IF(Destroyed())) {
3988 return NS_ERROR_EDITOR_DESTROYED;
3990 if (NS_FAILED(rv)) {
3991 NS_WARNING("HTMLEditor::DeleteTextWithTransaction() failed");
3992 return rv;
3994 continue;
3997 if (textNode == aEndPoint.GetContainer()) {
3998 if (aEndPoint.IsStartOfContainer()) {
3999 break;
4001 if (aEndPoint.IsEndOfContainer() &&
4002 aTreatEmptyTextNodes !=
4003 TreatEmptyTextNodes::KeepIfContainerOfRangeBoundaries) {
4004 nsresult rv = deleteEmptyContentNodeWithTransaction(
4005 MOZ_KnownLive(*aEndPoint.ContainerAsText()));
4006 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
4007 "deleteEmptyContentNodeWithTransaction() failed");
4008 return rv;
4010 nsresult rv = DeleteTextWithTransaction(MOZ_KnownLive(textNode), 0,
4011 aEndPoint.Offset());
4012 if (NS_WARN_IF(Destroyed())) {
4013 return NS_ERROR_EDITOR_DESTROYED;
4015 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
4016 "HTMLEditor::DeleteTextWithTransaction() failed");
4017 return rv;
4020 nsresult rv =
4021 deleteEmptyContentNodeWithTransaction(MOZ_KnownLive(textNode));
4022 if (NS_FAILED(rv)) {
4023 NS_WARNING("deleteEmptyContentNodeWithTransaction() failed");
4024 return rv;
4028 return NS_OK;
4031 Result<EditorDOMPoint, nsresult> HTMLEditor::AutoDeleteRangesHandler::
4032 AutoBlockElementsJoiner::JoinNodesDeepWithTransaction(
4033 HTMLEditor& aHTMLEditor, nsIContent& aLeftContent,
4034 nsIContent& aRightContent) {
4035 // While the rightmost children and their descendants of the left node match
4036 // the leftmost children and their descendants of the right node, join them
4037 // up.
4039 nsCOMPtr<nsIContent> leftContentToJoin = &aLeftContent;
4040 nsCOMPtr<nsIContent> rightContentToJoin = &aRightContent;
4041 nsCOMPtr<nsINode> parentNode = aRightContent.GetParentNode();
4043 EditorDOMPoint ret;
4044 const HTMLEditUtils::StyleDifference kCompareStyle =
4045 aHTMLEditor.IsCSSEnabled() ? StyleDifference::CompareIfSpanElements
4046 : StyleDifference::Ignore;
4047 while (leftContentToJoin && rightContentToJoin && parentNode &&
4048 HTMLEditUtils::CanContentsBeJoined(
4049 *leftContentToJoin, *rightContentToJoin, kCompareStyle)) {
4050 uint32_t length = leftContentToJoin->Length();
4052 // Do the join
4053 nsresult rv = aHTMLEditor.JoinNodesWithTransaction(*leftContentToJoin,
4054 *rightContentToJoin);
4055 if (NS_WARN_IF(aHTMLEditor.Destroyed())) {
4056 return Err(NS_ERROR_EDITOR_DESTROYED);
4058 if (NS_FAILED(rv)) {
4059 NS_WARNING("HTMLEditor::JoinNodesWithTransaction() failed");
4060 return Err(rv);
4063 // XXX rightContentToJoin may have fewer children or shorter length text.
4064 // So, we need some adjustment here.
4065 ret.Set(rightContentToJoin, length);
4066 if (NS_WARN_IF(!ret.IsSet())) {
4067 return Err(NS_ERROR_FAILURE);
4070 if (parentNode->IsText()) {
4071 // We've joined all the way down to text nodes, we're done!
4072 return ret;
4075 // Get new left and right nodes, and begin anew
4076 parentNode = rightContentToJoin;
4077 rightContentToJoin = parentNode->GetChildAt_Deprecated(length);
4078 if (rightContentToJoin) {
4079 leftContentToJoin = rightContentToJoin->GetPreviousSibling();
4080 } else {
4081 leftContentToJoin = nullptr;
4084 // Skip over non-editable nodes
4085 while (leftContentToJoin && !EditorUtils::IsEditableContent(
4086 *leftContentToJoin, EditorType::HTML)) {
4087 leftContentToJoin = leftContentToJoin->GetPreviousSibling();
4089 if (!leftContentToJoin) {
4090 return ret;
4093 while (rightContentToJoin && !EditorUtils::IsEditableContent(
4094 *rightContentToJoin, EditorType::HTML)) {
4095 rightContentToJoin = rightContentToJoin->GetNextSibling();
4097 if (!rightContentToJoin) {
4098 return ret;
4102 if (!ret.IsSet()) {
4103 NS_WARNING("HTMLEditor::JoinNodesDeepWithTransaction() joined no contents");
4104 return Err(NS_ERROR_FAILURE);
4106 return ret;
4109 Result<bool, nsresult> HTMLEditor::AutoDeleteRangesHandler::
4110 AutoBlockElementsJoiner::AutoInclusiveAncestorBlockElementsJoiner::Prepare(
4111 const HTMLEditor& aHTMLEditor) {
4112 mLeftBlockElement =
4113 HTMLEditUtils::GetInclusiveAncestorBlockElementExceptHRElement(
4114 mInclusiveDescendantOfLeftBlockElement);
4115 mRightBlockElement =
4116 HTMLEditUtils::GetInclusiveAncestorBlockElementExceptHRElement(
4117 mInclusiveDescendantOfRightBlockElement);
4119 if (NS_WARN_IF(!IsSet())) {
4120 mCanJoinBlocks = false;
4121 return Err(NS_ERROR_UNEXPECTED);
4124 // Don't join the blocks if both of them are basic structure of the HTML
4125 // document (Note that `<body>` can be joined with its children).
4126 if (mLeftBlockElement->IsAnyOfHTMLElements(nsGkAtoms::html, nsGkAtoms::head,
4127 nsGkAtoms::body) &&
4128 mRightBlockElement->IsAnyOfHTMLElements(nsGkAtoms::html, nsGkAtoms::head,
4129 nsGkAtoms::body)) {
4130 mCanJoinBlocks = false;
4131 return false;
4134 if (HTMLEditUtils::IsAnyTableElement(mLeftBlockElement) ||
4135 HTMLEditUtils::IsAnyTableElement(mRightBlockElement)) {
4136 // Do not try to merge table elements, cancel the deletion.
4137 mCanJoinBlocks = false;
4138 return false;
4141 // Bail if both blocks the same
4142 if (IsSameBlockElement()) {
4143 mCanJoinBlocks = true; // XXX Anyway, Run() will ingore this case.
4144 mFallbackToDeleteLeafContent = true;
4145 return true;
4148 // Joining a list item to its parent is a NOP.
4149 if (HTMLEditUtils::IsAnyListElement(mLeftBlockElement) &&
4150 HTMLEditUtils::IsListItem(mRightBlockElement) &&
4151 mRightBlockElement->GetParentNode() == mLeftBlockElement) {
4152 mCanJoinBlocks = false;
4153 return true;
4156 // Special rule here: if we are trying to join list items, and they are in
4157 // different lists, join the lists instead.
4158 if (HTMLEditUtils::IsListItem(mLeftBlockElement) &&
4159 HTMLEditUtils::IsListItem(mRightBlockElement)) {
4160 // XXX leftListElement and/or rightListElement may be not list elements.
4161 Element* leftListElement = mLeftBlockElement->GetParentElement();
4162 Element* rightListElement = mRightBlockElement->GetParentElement();
4163 EditorDOMPoint atChildInBlock;
4164 if (leftListElement && rightListElement &&
4165 leftListElement != rightListElement &&
4166 !EditorUtils::IsDescendantOf(*leftListElement, *mRightBlockElement,
4167 &atChildInBlock) &&
4168 !EditorUtils::IsDescendantOf(*rightListElement, *mLeftBlockElement,
4169 &atChildInBlock)) {
4170 // There are some special complications if the lists are descendants of
4171 // the other lists' items. Note that it is okay for them to be
4172 // descendants of the other lists themselves, which is the usual case for
4173 // sublists in our implementation.
4174 MOZ_DIAGNOSTIC_ASSERT(!atChildInBlock.IsSet());
4175 mLeftBlockElement = leftListElement;
4176 mRightBlockElement = rightListElement;
4177 mNewListElementTagNameOfRightListElement =
4178 Some(leftListElement->NodeInfo()->NameAtom());
4182 if (!EditorUtils::IsDescendantOf(*mLeftBlockElement, *mRightBlockElement,
4183 &mPointContainingTheOtherBlockElement)) {
4184 Unused << EditorUtils::IsDescendantOf(
4185 *mRightBlockElement, *mLeftBlockElement,
4186 &mPointContainingTheOtherBlockElement);
4189 if (mPointContainingTheOtherBlockElement.GetContainer() ==
4190 mRightBlockElement) {
4191 mPrecedingInvisibleBRElement =
4192 WSRunScanner::GetPrecedingBRElementUnlessVisibleContentFound(
4193 aHTMLEditor, EditorDOMPoint::AtEndOf(mLeftBlockElement));
4194 // `WhiteSpaceVisibilityKeeper::
4195 // MergeFirstLineOfRightBlockElementIntoDescendantLeftBlockElement()`
4196 // returns ignored when:
4197 // - No preceding invisible `<br>` element and
4198 // - mNewListElementTagNameOfRightListElement is nothing and
4199 // - There is no content to move from right block element.
4200 if (!mPrecedingInvisibleBRElement) {
4201 if (CanMergeLeftAndRightBlockElements()) {
4202 // Always marked as handled in this case.
4203 mFallbackToDeleteLeafContent = false;
4204 } else {
4205 // Marked as handled only when it actually moves a content node.
4206 Result<bool, nsresult> firstLineHasContent =
4207 aHTMLEditor.CanMoveOrDeleteSomethingInHardLine(
4208 mPointContainingTheOtherBlockElement.NextPoint());
4209 mFallbackToDeleteLeafContent =
4210 firstLineHasContent.isOk() && !firstLineHasContent.inspect();
4212 } else {
4213 // Marked as handled when deleting the invisible `<br>` element.
4214 mFallbackToDeleteLeafContent = false;
4216 } else if (mPointContainingTheOtherBlockElement.GetContainer() ==
4217 mLeftBlockElement) {
4218 mPrecedingInvisibleBRElement =
4219 WSRunScanner::GetPrecedingBRElementUnlessVisibleContentFound(
4220 aHTMLEditor, mPointContainingTheOtherBlockElement);
4221 // `WhiteSpaceVisibilityKeeper::
4222 // MergeFirstLineOfRightBlockElementIntoAncestorLeftBlockElement()`
4223 // returns ignored when:
4224 // - No preceding invisible `<br>` element and
4225 // - mNewListElementTagNameOfRightListElement is some and
4226 // - The right block element has no children
4227 // or,
4228 // - No preceding invisible `<br>` element and
4229 // - mNewListElementTagNameOfRightListElement is nothing and
4230 // - There is no content to move from right block element.
4231 if (!mPrecedingInvisibleBRElement) {
4232 if (CanMergeLeftAndRightBlockElements()) {
4233 // Marked as handled only when it actualy moves a content node.
4234 Result<bool, nsresult> rightBlockHasContent =
4235 aHTMLEditor.CanMoveChildren(*mRightBlockElement,
4236 *mLeftBlockElement);
4237 mFallbackToDeleteLeafContent =
4238 rightBlockHasContent.isOk() && !rightBlockHasContent.inspect();
4239 } else {
4240 // Marked as handled only when it actually moves a content node.
4241 Result<bool, nsresult> firstLineHasContent =
4242 aHTMLEditor.CanMoveOrDeleteSomethingInHardLine(
4243 EditorRawDOMPoint(mRightBlockElement, 0));
4244 mFallbackToDeleteLeafContent =
4245 firstLineHasContent.isOk() && !firstLineHasContent.inspect();
4247 } else {
4248 // Marked as handled when deleting the invisible `<br>` element.
4249 mFallbackToDeleteLeafContent = false;
4251 } else {
4252 mPrecedingInvisibleBRElement =
4253 WSRunScanner::GetPrecedingBRElementUnlessVisibleContentFound(
4254 aHTMLEditor, EditorDOMPoint::AtEndOf(mLeftBlockElement));
4255 // `WhiteSpaceVisibilityKeeper::
4256 // MergeFirstLineOfRightBlockElementIntoLeftBlockElement()` always
4257 // return "handled".
4258 mFallbackToDeleteLeafContent = false;
4261 mCanJoinBlocks = true;
4262 return true;
4265 nsresult HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
4266 AutoInclusiveAncestorBlockElementsJoiner::ComputeRangesToDelete(
4267 const HTMLEditor& aHTMLEditor, const EditorDOMPoint& aCaretPoint,
4268 AutoRangeArray& aRangesToDelete) const {
4269 MOZ_ASSERT(!aRangesToDelete.Ranges().IsEmpty());
4270 MOZ_ASSERT(mLeftBlockElement);
4271 MOZ_ASSERT(mRightBlockElement);
4273 if (IsSameBlockElement()) {
4274 if (!aCaretPoint.IsSet()) {
4275 return NS_OK; // The ranges are not collapsed, keep them as-is.
4277 nsresult rv = aRangesToDelete.Collapse(aCaretPoint);
4278 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "AutoRangeArray::Collapse() failed");
4279 return rv;
4282 EditorDOMPoint pointContainingTheOtherBlock;
4283 if (!EditorUtils::IsDescendantOf(*mLeftBlockElement, *mRightBlockElement,
4284 &pointContainingTheOtherBlock)) {
4285 Unused << EditorUtils::IsDescendantOf(
4286 *mRightBlockElement, *mLeftBlockElement, &pointContainingTheOtherBlock);
4288 EditorDOMRange range =
4289 WSRunScanner::GetRangeForDeletingBlockElementBoundaries(
4290 aHTMLEditor, *mLeftBlockElement, *mRightBlockElement,
4291 pointContainingTheOtherBlock);
4292 if (!range.IsPositioned()) {
4293 NS_WARNING(
4294 "WSRunScanner::GetRangeForDeletingBlockElementBoundaries() failed");
4295 return NS_ERROR_FAILURE;
4297 if (!aCaretPoint.IsSet()) {
4298 // Don't shrink the original range.
4299 bool noNeedToChangeStart = false;
4300 EditorDOMPoint atStart(aRangesToDelete.GetStartPointOfFirstRange());
4301 if (atStart.IsBefore(range.StartRef())) {
4302 // If the range starts from end of a container, and computed block
4303 // boundaries range starts from an invisible `<br>` element, we
4304 // may need to shrink the range.
4305 nsIContent* nextContent =
4306 atStart.IsEndOfContainer() && range.StartRef().GetChild() &&
4307 range.StartRef().GetChild()->IsHTMLElement(nsGkAtoms::br) &&
4308 !aHTMLEditor.IsVisibleBRElement(range.StartRef().GetChild())
4309 ? aHTMLEditor.GetNextElementOrTextInBlock(
4310 *atStart.ContainerAsContent())
4311 : nullptr;
4312 if (!nextContent || nextContent != range.StartRef().GetChild()) {
4313 noNeedToChangeStart = true;
4314 range.SetStart(aRangesToDelete.GetStartPointOfFirstRange());
4317 if (range.EndRef().IsBefore(aRangesToDelete.GetEndPointOfFirstRange())) {
4318 if (noNeedToChangeStart) {
4319 return NS_OK; // We don't need to modify the range.
4321 range.SetEnd(aRangesToDelete.GetEndPointOfFirstRange());
4324 // XXX Oddly, we join blocks only at the first range.
4325 nsresult rv = aRangesToDelete.FirstRangeRef()->SetStartAndEnd(
4326 range.StartRef().ToRawRangeBoundary(),
4327 range.EndRef().ToRawRangeBoundary());
4328 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
4329 "AutoRangeArray::SetStartAndEnd() failed");
4330 return rv;
4333 EditActionResult HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
4334 AutoInclusiveAncestorBlockElementsJoiner::Run(HTMLEditor& aHTMLEditor) {
4335 MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable());
4336 MOZ_ASSERT(mLeftBlockElement);
4337 MOZ_ASSERT(mRightBlockElement);
4339 if (IsSameBlockElement()) {
4340 return EditActionIgnored();
4343 if (!mCanJoinBlocks) {
4344 return EditActionHandled();
4347 // If the left block element is in the right block element, move the hard
4348 // line including the right block element to end of the left block.
4349 // However, if we are merging list elements, we don't join them.
4350 if (mPointContainingTheOtherBlockElement.GetContainer() ==
4351 mRightBlockElement) {
4352 EditActionResult result = WhiteSpaceVisibilityKeeper::
4353 MergeFirstLineOfRightBlockElementIntoDescendantLeftBlockElement(
4354 aHTMLEditor, MOZ_KnownLive(*mLeftBlockElement),
4355 MOZ_KnownLive(*mRightBlockElement),
4356 mPointContainingTheOtherBlockElement,
4357 mNewListElementTagNameOfRightListElement,
4358 MOZ_KnownLive(mPrecedingInvisibleBRElement));
4359 NS_WARNING_ASSERTION(result.Succeeded(),
4360 "WhiteSpaceVisibilityKeeper::"
4361 "MergeFirstLineOfRightBlockElementIntoDescendantLeftBl"
4362 "ockElement() failed");
4363 return result;
4366 // If the right block element is in the left block element:
4367 // - move list item elements in the right block element to where the left
4368 // list element is
4369 // - or first hard line in the right block element to where:
4370 // - the left block element is.
4371 // - or the given left content in the left block is.
4372 if (mPointContainingTheOtherBlockElement.GetContainer() ==
4373 mLeftBlockElement) {
4374 EditActionResult result = WhiteSpaceVisibilityKeeper::
4375 MergeFirstLineOfRightBlockElementIntoAncestorLeftBlockElement(
4376 aHTMLEditor, MOZ_KnownLive(*mLeftBlockElement),
4377 MOZ_KnownLive(*mRightBlockElement),
4378 mPointContainingTheOtherBlockElement,
4379 MOZ_KnownLive(*mInclusiveDescendantOfLeftBlockElement),
4380 mNewListElementTagNameOfRightListElement,
4381 MOZ_KnownLive(mPrecedingInvisibleBRElement));
4382 NS_WARNING_ASSERTION(result.Succeeded(),
4383 "WhiteSpaceVisibilityKeeper::"
4384 "MergeFirstLineOfRightBlockElementIntoAncestorLeftBloc"
4385 "kElement() failed");
4386 return result;
4389 MOZ_ASSERT(!mPointContainingTheOtherBlockElement.IsSet());
4391 // Normal case. Blocks are siblings, or at least close enough. An example
4392 // of the latter is <p>paragraph</p><ul><li>one<li>two<li>three</ul>. The
4393 // first li and the p are not true siblings, but we still want to join them
4394 // if you backspace from li into p.
4395 EditActionResult result = WhiteSpaceVisibilityKeeper::
4396 MergeFirstLineOfRightBlockElementIntoLeftBlockElement(
4397 aHTMLEditor, MOZ_KnownLive(*mLeftBlockElement),
4398 MOZ_KnownLive(*mRightBlockElement),
4399 mNewListElementTagNameOfRightListElement,
4400 MOZ_KnownLive(mPrecedingInvisibleBRElement));
4401 NS_WARNING_ASSERTION(
4402 result.Succeeded(),
4403 "WhiteSpaceVisibilityKeeper::"
4404 "MergeFirstLineOfRightBlockElementIntoLeftBlockElement() failed");
4405 return result;
4408 template <typename PT, typename CT>
4409 Result<bool, nsresult> HTMLEditor::CanMoveOrDeleteSomethingInHardLine(
4410 const EditorDOMPointBase<PT, CT>& aPointInHardLine) const {
4411 RefPtr<nsRange> oneLineRange = CreateRangeExtendedToHardLineStartAndEnd(
4412 aPointInHardLine.ToRawRangeBoundary(),
4413 aPointInHardLine.ToRawRangeBoundary(),
4414 EditSubAction::eMergeBlockContents);
4415 if (!oneLineRange || oneLineRange->Collapsed() ||
4416 !oneLineRange->IsPositioned() ||
4417 !oneLineRange->GetStartContainer()->IsContent() ||
4418 !oneLineRange->GetEndContainer()->IsContent()) {
4419 return false;
4422 // If there is only a padding `<br>` element in a empty block, it's selected
4423 // by `SelectBRElementIfCollapsedInEmptyBlock()`. However, it won't be
4424 // moved. Although it'll be deleted, `MoveOneHardLineContents()` returns
4425 // "ignored". Therefore, we should return `false` in this case.
4426 if (nsIContent* childContent = oneLineRange->GetChildAtStartOffset()) {
4427 if (childContent->IsHTMLElement(nsGkAtoms::br) &&
4428 childContent->GetParent()) {
4429 if (Element* blockElement =
4430 HTMLEditUtils::GetInclusiveAncestorBlockElement(
4431 *childContent->GetParent())) {
4432 if (IsEmptyNode(*blockElement, true, false)) {
4433 return false;
4439 nsINode* commonAncestor = oneLineRange->GetClosestCommonInclusiveAncestor();
4440 // Currently, we move non-editable content nodes too.
4441 EditorRawDOMPoint startPoint(oneLineRange->StartRef());
4442 if (!startPoint.IsEndOfContainer()) {
4443 return true;
4445 EditorRawDOMPoint endPoint(oneLineRange->EndRef());
4446 if (!endPoint.IsStartOfContainer()) {
4447 return true;
4449 if (startPoint.GetContainer() != commonAncestor) {
4450 while (true) {
4451 EditorRawDOMPoint pointInParent(startPoint.GetContainerAsContent());
4452 if (NS_WARN_IF(!pointInParent.IsInContentNode())) {
4453 return Err(NS_ERROR_FAILURE);
4455 if (pointInParent.GetContainer() == commonAncestor) {
4456 startPoint = pointInParent;
4457 break;
4459 if (!pointInParent.IsEndOfContainer()) {
4460 return true;
4464 if (endPoint.GetContainer() != commonAncestor) {
4465 while (true) {
4466 EditorRawDOMPoint pointInParent(endPoint.GetContainerAsContent());
4467 if (NS_WARN_IF(!pointInParent.IsInContentNode())) {
4468 return Err(NS_ERROR_FAILURE);
4470 if (pointInParent.GetContainer() == commonAncestor) {
4471 endPoint = pointInParent;
4472 break;
4474 if (!pointInParent.IsStartOfContainer()) {
4475 return true;
4479 // If start point and end point in the common ancestor are direct siblings,
4480 // there is no content to move or delete.
4481 // E.g., `<b>abc<br>[</b><i>]<br>def</i>`.
4482 return startPoint.GetNextSiblingOfChild() != endPoint.GetChild();
4485 MoveNodeResult HTMLEditor::MoveOneHardLineContents(
4486 const EditorDOMPoint& aPointInHardLine,
4487 const EditorDOMPoint& aPointToInsert,
4488 MoveToEndOfContainer
4489 aMoveToEndOfContainer /* = MoveToEndOfContainer::No */) {
4490 MOZ_ASSERT(IsEditActionDataAvailable());
4492 AutoTArray<OwningNonNull<nsIContent>, 64> arrayOfContents;
4493 nsresult rv = SplitInlinesAndCollectEditTargetNodesInOneHardLine(
4494 aPointInHardLine, arrayOfContents, EditSubAction::eMergeBlockContents,
4495 HTMLEditor::CollectNonEditableNodes::Yes);
4496 if (NS_FAILED(rv)) {
4497 NS_WARNING(
4498 "HTMLEditor::SplitInlinesAndCollectEditTargetNodesInOneHardLine("
4499 "eMergeBlockContents, CollectNonEditableNodes::Yes) failed");
4500 return MoveNodeResult(rv);
4502 if (arrayOfContents.IsEmpty()) {
4503 return MoveNodeIgnored(aPointToInsert);
4506 uint32_t offset = aPointToInsert.Offset();
4507 MoveNodeResult result;
4508 for (auto& content : arrayOfContents) {
4509 if (aMoveToEndOfContainer == MoveToEndOfContainer::Yes) {
4510 // For backward compatibility, we should move contents to end of the
4511 // container if this is called with MoveToEndOfContainer::Yes.
4512 offset = aPointToInsert.GetContainer()->Length();
4514 // get the node to act on
4515 if (HTMLEditUtils::IsBlockElement(content)) {
4516 // For block nodes, move their contents only, then delete block.
4517 result |= MoveChildrenWithTransaction(
4518 MOZ_KnownLive(*content->AsElement()),
4519 EditorDOMPoint(aPointToInsert.GetContainer(), offset));
4520 if (result.Failed()) {
4521 NS_WARNING("HTMLEditor::MoveChildrenWithTransaction() failed");
4522 return result;
4524 offset = result.NextInsertionPointRef().Offset();
4525 // MOZ_KnownLive because 'arrayOfContents' is guaranteed to
4526 // keep it alive.
4527 DebugOnly<nsresult> rvIgnored =
4528 DeleteNodeWithTransaction(MOZ_KnownLive(*content));
4529 if (NS_WARN_IF(Destroyed())) {
4530 return MoveNodeResult(NS_ERROR_EDITOR_DESTROYED);
4532 NS_WARNING_ASSERTION(
4533 NS_SUCCEEDED(rvIgnored),
4534 "HTMLEditor::DeleteNodeWithTransaction() failed, but ignored");
4535 result.MarkAsHandled();
4536 if (MayHaveMutationEventListeners()) {
4537 // Mutation event listener may make `offset` value invalid with
4538 // removing some previous children while we call
4539 // `DeleteNodeWithTransaction()` so that we should adjust it here.
4540 offset = std::min(offset, aPointToInsert.GetContainer()->Length());
4542 continue;
4544 // XXX Different from the above block, we ignore error of moving nodes.
4545 // MOZ_KnownLive because 'arrayOfContents' is guaranteed to
4546 // keep it alive.
4547 MoveNodeResult moveNodeResult = MoveNodeOrChildrenWithTransaction(
4548 MOZ_KnownLive(content),
4549 EditorDOMPoint(aPointToInsert.GetContainer(), offset));
4550 if (NS_WARN_IF(moveNodeResult.EditorDestroyed())) {
4551 return MoveNodeResult(NS_ERROR_EDITOR_DESTROYED);
4553 NS_WARNING_ASSERTION(
4554 moveNodeResult.Succeeded(),
4555 "HTMLEditor::MoveNodeOrChildrenWithTransaction() failed, but ignored");
4556 if (moveNodeResult.Succeeded()) {
4557 offset = moveNodeResult.NextInsertionPointRef().Offset();
4558 result |= moveNodeResult;
4562 NS_WARNING_ASSERTION(
4563 result.Succeeded(),
4564 "Last HTMLEditor::MoveNodeOrChildrenWithTransaction() failed");
4565 return result;
4568 Result<bool, nsresult> HTMLEditor::CanMoveNodeOrChildren(
4569 const nsIContent& aContent, const nsINode& aNewContainer) const {
4570 if (HTMLEditUtils::CanNodeContain(aNewContainer, aContent)) {
4571 return true;
4573 if (aContent.IsElement()) {
4574 return CanMoveChildren(*aContent.AsElement(), aNewContainer);
4576 return true;
4579 MoveNodeResult HTMLEditor::MoveNodeOrChildrenWithTransaction(
4580 nsIContent& aContent, const EditorDOMPoint& aPointToInsert) {
4581 MOZ_ASSERT(IsEditActionDataAvailable());
4582 MOZ_ASSERT(aPointToInsert.IsSet());
4584 // Check if this node can go into the destination node
4585 if (HTMLEditUtils::CanNodeContain(*aPointToInsert.GetContainer(), aContent)) {
4586 // If it can, move it there.
4587 uint32_t offsetAtInserting = aPointToInsert.Offset();
4588 nsresult rv = MoveNodeWithTransaction(aContent, aPointToInsert);
4589 if (NS_WARN_IF(Destroyed())) {
4590 return MoveNodeResult(NS_ERROR_EDITOR_DESTROYED);
4592 if (NS_FAILED(rv)) {
4593 NS_WARNING("HTMLEditor::MoveNodeWithTransaction() failed");
4594 return MoveNodeResult(rv);
4596 // Advance DOM point with offset for keeping backward compatibility.
4597 // XXX Where should we insert next content if a mutation event listener
4598 // break the relation of offset and moved node?
4599 return MoveNodeHandled(aPointToInsert.GetContainer(), ++offsetAtInserting);
4602 // If it can't, move its children (if any), and then delete it.
4603 MoveNodeResult result;
4604 if (aContent.IsElement()) {
4605 result = MoveChildrenWithTransaction(MOZ_KnownLive(*aContent.AsElement()),
4606 aPointToInsert);
4607 if (result.Failed()) {
4608 NS_WARNING("HTMLEditor::MoveChildrenWithTransaction() failed");
4609 return result;
4611 } else {
4612 result = MoveNodeHandled(aPointToInsert);
4615 nsresult rv = DeleteNodeWithTransaction(aContent);
4616 if (NS_WARN_IF(Destroyed())) {
4617 return MoveNodeResult(NS_ERROR_EDITOR_DESTROYED);
4619 if (NS_FAILED(rv)) {
4620 NS_WARNING("HTMLEditor::DeleteNodeWithTransaction() failed");
4621 return MoveNodeResult(rv);
4623 if (MayHaveMutationEventListeners()) {
4624 // Mutation event listener may make `offset` value invalid with
4625 // removing some previous children while we call
4626 // `DeleteNodeWithTransaction()` so that we should adjust it here.
4627 if (!result.NextInsertionPointRef().IsSetAndValid()) {
4628 result = MoveNodeHandled(
4629 EditorDOMPoint::AtEndOf(*aPointToInsert.GetContainer()));
4632 return result;
4635 Result<bool, nsresult> HTMLEditor::CanMoveChildren(
4636 const Element& aElement, const nsINode& aNewContainer) const {
4637 if (NS_WARN_IF(&aElement == &aNewContainer)) {
4638 return Err(NS_ERROR_FAILURE);
4640 for (nsIContent* childContent = aElement.GetFirstChild(); childContent;
4641 childContent = childContent->GetNextSibling()) {
4642 Result<bool, nsresult> result =
4643 CanMoveNodeOrChildren(*childContent, aNewContainer);
4644 if (result.isErr() || result.inspect()) {
4645 return result;
4648 return false;
4651 MoveNodeResult HTMLEditor::MoveChildrenWithTransaction(
4652 Element& aElement, const EditorDOMPoint& aPointToInsert) {
4653 MOZ_ASSERT(aPointToInsert.IsSet());
4655 if (NS_WARN_IF(&aElement == aPointToInsert.GetContainer())) {
4656 return MoveNodeResult(NS_ERROR_INVALID_ARG);
4659 MoveNodeResult result = MoveNodeIgnored(aPointToInsert);
4660 while (aElement.GetFirstChild()) {
4661 result |= MoveNodeOrChildrenWithTransaction(
4662 MOZ_KnownLive(*aElement.GetFirstChild()), result.NextInsertionPoint());
4663 if (result.Failed()) {
4664 NS_WARNING("HTMLEditor::MoveNodeOrChildrenWithTransaction() failed");
4665 return result;
4668 return result;
4671 void HTMLEditor::MoveAllChildren(nsINode& aContainer,
4672 const EditorRawDOMPoint& aPointToInsert,
4673 ErrorResult& aError) {
4674 MOZ_ASSERT(!aError.Failed());
4676 if (!aContainer.HasChildren()) {
4677 return;
4679 nsIContent* firstChild = aContainer.GetFirstChild();
4680 if (NS_WARN_IF(!firstChild)) {
4681 aError.Throw(NS_ERROR_FAILURE);
4682 return;
4684 nsIContent* lastChild = aContainer.GetLastChild();
4685 if (NS_WARN_IF(!lastChild)) {
4686 aError.Throw(NS_ERROR_FAILURE);
4687 return;
4689 MoveChildrenBetween(*firstChild, *lastChild, aPointToInsert, aError);
4690 NS_WARNING_ASSERTION(!aError.Failed(),
4691 "HTMLEditor::MoveChildrenBetween() failed");
4694 void HTMLEditor::MoveChildrenBetween(nsIContent& aFirstChild,
4695 nsIContent& aLastChild,
4696 const EditorRawDOMPoint& aPointToInsert,
4697 ErrorResult& aError) {
4698 nsCOMPtr<nsINode> oldContainer = aFirstChild.GetParentNode();
4699 if (NS_WARN_IF(oldContainer != aLastChild.GetParentNode()) ||
4700 NS_WARN_IF(!aPointToInsert.IsSet()) ||
4701 NS_WARN_IF(!aPointToInsert.CanContainerHaveChildren())) {
4702 aError.Throw(NS_ERROR_INVALID_ARG);
4703 return;
4706 // First, store all children which should be moved to the new container.
4707 AutoTArray<nsCOMPtr<nsIContent>, 10> children;
4708 for (nsIContent* child = &aFirstChild; child;
4709 child = child->GetNextSibling()) {
4710 children.AppendElement(child);
4711 if (child == &aLastChild) {
4712 break;
4716 if (NS_WARN_IF(children.LastElement() != &aLastChild)) {
4717 aError.Throw(NS_ERROR_INVALID_ARG);
4718 return;
4721 nsCOMPtr<nsINode> newContainer = aPointToInsert.GetContainer();
4722 nsCOMPtr<nsIContent> nextNode = aPointToInsert.GetChild();
4723 for (size_t i = children.Length(); i > 0; --i) {
4724 nsCOMPtr<nsIContent>& child = children[i - 1];
4725 if (child->GetParentNode() != oldContainer) {
4726 // If the child has been moved to different container, we shouldn't
4727 // touch it.
4728 continue;
4730 oldContainer->RemoveChild(*child, aError);
4731 if (aError.Failed()) {
4732 NS_WARNING("nsINode::RemoveChild() failed");
4733 return;
4735 if (nextNode) {
4736 // If we're not appending the children to the new container, we should
4737 // check if referring next node of insertion point is still in the new
4738 // container.
4739 EditorRawDOMPoint pointToInsert(nextNode);
4740 if (NS_WARN_IF(!pointToInsert.IsSet()) ||
4741 NS_WARN_IF(pointToInsert.GetContainer() != newContainer)) {
4742 // The next node of insertion point has been moved by mutation observer.
4743 // Let's stop moving the remaining nodes.
4744 // XXX Or should we move remaining children after the last moved child?
4745 aError.Throw(NS_ERROR_FAILURE);
4746 return;
4749 newContainer->InsertBefore(*child, nextNode, aError);
4750 if (aError.Failed()) {
4751 NS_WARNING("nsINode::InsertBefore() failed");
4752 return;
4754 // If the child was inserted or appended properly, the following children
4755 // should be inserted before it. Otherwise, keep using current position.
4756 if (child->GetParentNode() == newContainer) {
4757 nextNode = child;
4762 void HTMLEditor::MovePreviousSiblings(nsIContent& aChild,
4763 const EditorRawDOMPoint& aPointToInsert,
4764 ErrorResult& aError) {
4765 MOZ_ASSERT(!aError.Failed());
4767 if (NS_WARN_IF(!aChild.GetParentNode())) {
4768 aError.Throw(NS_ERROR_INVALID_ARG);
4769 return;
4771 nsIContent* firstChild = aChild.GetParentNode()->GetFirstChild();
4772 if (NS_WARN_IF(!firstChild)) {
4773 aError.Throw(NS_ERROR_FAILURE);
4774 return;
4776 nsIContent* lastChild =
4777 &aChild == firstChild ? firstChild : aChild.GetPreviousSibling();
4778 if (NS_WARN_IF(!lastChild)) {
4779 aError.Throw(NS_ERROR_FAILURE);
4780 return;
4782 MoveChildrenBetween(*firstChild, *lastChild, aPointToInsert, aError);
4783 NS_WARNING_ASSERTION(!aError.Failed(),
4784 "HTMLEditor::MoveChildrenBetween() failed");
4787 nsresult HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
4788 DeleteContentButKeepTableStructure(HTMLEditor& aHTMLEditor,
4789 nsIContent& aContent) {
4790 MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable());
4792 if (!HTMLEditUtils::IsAnyTableElementButNotTable(&aContent)) {
4793 nsresult rv = aHTMLEditor.DeleteNodeWithTransaction(aContent);
4794 if (NS_WARN_IF(aHTMLEditor.Destroyed())) {
4795 return NS_ERROR_EDITOR_DESTROYED;
4797 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
4798 "HTMLEditor::DeleteNodeWithTransaction() failed");
4799 return rv;
4802 // XXX For performance, this should just call
4803 // DeleteContentButKeepTableStructure() while there are children in
4804 // aContent. If we need to avoid infinite loop because mutation event
4805 // listeners can add unexpected nodes into aContent, we should just loop
4806 // only original count of the children.
4807 AutoTArray<OwningNonNull<nsIContent>, 10> childList;
4808 for (nsIContent* child = aContent.GetFirstChild(); child;
4809 child = child->GetNextSibling()) {
4810 childList.AppendElement(*child);
4813 for (const auto& child : childList) {
4814 // MOZ_KnownLive because 'childList' is guaranteed to
4815 // keep it alive.
4816 nsresult rv =
4817 DeleteContentButKeepTableStructure(aHTMLEditor, MOZ_KnownLive(child));
4818 if (NS_FAILED(rv)) {
4819 NS_WARNING("HTMLEditor::DeleteContentButKeepTableStructure() failed");
4820 return rv;
4823 return NS_OK;
4826 nsresult HTMLEditor::DeleteMostAncestorMailCiteElementIfEmpty(
4827 nsIContent& aContent) {
4828 MOZ_ASSERT(IsEditActionDataAvailable());
4830 // The element must be `<blockquote type="cite">` or
4831 // `<span _moz_quote="true">`.
4832 RefPtr<Element> mailCiteElement = GetMostAncestorMailCiteElement(aContent);
4833 if (!mailCiteElement) {
4834 return NS_OK;
4836 bool seenBR = false;
4837 if (!IsEmptyNodeImpl(*mailCiteElement, true, true, false, &seenBR)) {
4838 return NS_OK;
4840 EditorDOMPoint atEmptyMailCiteElement(mailCiteElement);
4842 AutoEditorDOMPointChildInvalidator lockOffset(atEmptyMailCiteElement);
4843 nsresult rv = DeleteNodeWithTransaction(*mailCiteElement);
4844 if (NS_WARN_IF(Destroyed())) {
4845 return NS_ERROR_EDITOR_DESTROYED;
4847 if (NS_FAILED(rv)) {
4848 NS_WARNING("HTMLEditor::DeleteNodeWithTransaction() failed");
4849 return rv;
4853 if (!atEmptyMailCiteElement.IsSet() || !seenBR) {
4854 NS_WARNING_ASSERTION(
4855 atEmptyMailCiteElement.IsSet(),
4856 "Mutation event listener might changed the DOM tree during "
4857 "HTMLEditor::DeleteNodeWithTransaction(), but ignored");
4858 return NS_OK;
4861 RefPtr<Element> brElement =
4862 InsertBRElementWithTransaction(atEmptyMailCiteElement);
4863 if (NS_WARN_IF(Destroyed())) {
4864 return NS_ERROR_EDITOR_DESTROYED;
4866 if (!brElement) {
4867 NS_WARNING("HTMLEditor::InsertBRElementWithTransaction() failed");
4868 return NS_ERROR_FAILURE;
4870 nsresult rv = CollapseSelectionTo(EditorRawDOMPoint(brElement));
4871 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
4872 return NS_ERROR_EDITOR_DESTROYED;
4874 NS_WARNING_ASSERTION(
4875 NS_SUCCEEDED(rv),
4876 "HTMLEditor::::CollapseSelectionTo() failed, but ignored");
4877 return NS_OK;
4880 Element* HTMLEditor::AutoDeleteRangesHandler::AutoEmptyBlockAncestorDeleter::
4881 ScanEmptyBlockInclusiveAncestor(const HTMLEditor& aHTMLEditor,
4882 nsIContent& aStartContent,
4883 Element& aEditingHostElement) {
4884 MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable());
4886 // If the editing host is an inline element, bail out early.
4887 if (HTMLEditUtils::IsInlineElement(aEditingHostElement)) {
4888 return nullptr;
4891 // If we are inside an empty block, delete it. Note: do NOT delete table
4892 // elements this way.
4893 Element* blockElement =
4894 HTMLEditUtils::GetInclusiveAncestorBlockElement(aStartContent);
4895 if (!blockElement) {
4896 return nullptr;
4898 while (blockElement && blockElement != &aEditingHostElement &&
4899 !HTMLEditUtils::IsAnyTableElement(blockElement) &&
4900 aHTMLEditor.IsEmptyNode(*blockElement, true, false)) {
4901 mEmptyInclusiveAncestorBlockElement = blockElement;
4902 blockElement = HTMLEditUtils::GetAncestorBlockElement(
4903 *mEmptyInclusiveAncestorBlockElement);
4905 if (!mEmptyInclusiveAncestorBlockElement) {
4906 return nullptr;
4909 // XXX Because of not checking whether found block element is editable
4910 // in the above loop, empty ediable block element may be overwritten
4911 // with empty non-editable clock element. Therefore, we fail to
4912 // remove the found empty nodes.
4913 if (NS_WARN_IF(!mEmptyInclusiveAncestorBlockElement->IsEditable()) ||
4914 NS_WARN_IF(!mEmptyInclusiveAncestorBlockElement->GetParentElement())) {
4915 mEmptyInclusiveAncestorBlockElement = nullptr;
4917 return mEmptyInclusiveAncestorBlockElement;
4920 nsresult HTMLEditor::AutoDeleteRangesHandler::AutoEmptyBlockAncestorDeleter::
4921 ComputeTargetRanges(const HTMLEditor& aHTMLEditor,
4922 nsIEditor::EDirection aDirectionAndAmount,
4923 const Element& aEditingHost,
4924 AutoRangeArray& aRangesToDelete) const {
4925 MOZ_ASSERT(mEmptyInclusiveAncestorBlockElement);
4927 // We'll delete `mEmptyInclusiveAncestorBlockElement` node from the tree, but
4928 // we should return the range from start/end of next/previous editable content
4929 // to end/start of the element for compatiblity with the other browsers.
4930 switch (aDirectionAndAmount) {
4931 case nsIEditor::eNone:
4932 break;
4933 case nsIEditor::ePrevious:
4934 case nsIEditor::ePreviousWord:
4935 case nsIEditor::eToBeginningOfLine: {
4936 EditorRawDOMPoint startPoint =
4937 HTMLEditUtils::GetPreviousEditablePoint<EditorRawDOMPoint>(
4938 *mEmptyInclusiveAncestorBlockElement, &aEditingHost,
4939 // In this case, we don't join block elements so that we won't
4940 // delete invisible trailing whitespaces in the previous element.
4941 InvisibleWhiteSpaces::Preserve,
4942 // In this case, we won't join table cells so that we should
4943 // get a range which is in a table cell even if it's in a
4944 // table.
4945 TableBoundary::NoCrossAnyTableElement);
4946 if (!startPoint.IsSet()) {
4947 NS_WARNING(
4948 "HTMLEditUtils::GetPreviousEditablePoint() didn't return a valid "
4949 "point");
4950 return NS_ERROR_FAILURE;
4952 nsresult rv = aRangesToDelete.SetStartAndEnd(
4953 startPoint,
4954 EditorRawDOMPoint::AtEndOf(mEmptyInclusiveAncestorBlockElement));
4955 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
4956 "AutoRangeArray::SetStartAndEnd() failed");
4957 return rv;
4959 case nsIEditor::eNext:
4960 case nsIEditor::eNextWord:
4961 case nsIEditor::eToEndOfLine: {
4962 EditorRawDOMPoint endPoint =
4963 HTMLEditUtils::GetNextEditablePoint<EditorRawDOMPoint>(
4964 *mEmptyInclusiveAncestorBlockElement, &aEditingHost,
4965 // In this case, we don't join block elements so that we won't
4966 // delete invisible trailing whitespaces in the next element.
4967 InvisibleWhiteSpaces::Preserve,
4968 // In this case, we won't join table cells so that we should
4969 // get a range which is in a table cell even if it's in a
4970 // table.
4971 TableBoundary::NoCrossAnyTableElement);
4972 if (!endPoint.IsSet()) {
4973 NS_WARNING(
4974 "HTMLEditUtils::GetNextEditablePoint() didn't return a valid "
4975 "point");
4976 return NS_ERROR_FAILURE;
4978 nsresult rv = aRangesToDelete.SetStartAndEnd(
4979 EditorRawDOMPoint(mEmptyInclusiveAncestorBlockElement, 0), endPoint);
4980 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
4981 "AutoRangeArray::SetStartAndEnd() failed");
4982 return rv;
4984 default:
4985 MOZ_ASSERT_UNREACHABLE("Handle the nsIEditor::EDirection value");
4986 break;
4988 // No direction, let's select the element to be deleted.
4989 nsresult rv =
4990 aRangesToDelete.SelectNode(*mEmptyInclusiveAncestorBlockElement);
4991 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "AutoRangeArray::SelectNode() failed");
4992 return rv;
4995 Result<RefPtr<Element>, nsresult>
4996 HTMLEditor::AutoDeleteRangesHandler::AutoEmptyBlockAncestorDeleter::
4997 MaybeInsertBRElementBeforeEmptyListItemElement(HTMLEditor& aHTMLEditor) {
4998 MOZ_ASSERT(mEmptyInclusiveAncestorBlockElement);
4999 MOZ_ASSERT(mEmptyInclusiveAncestorBlockElement->GetParentElement());
5000 MOZ_ASSERT(HTMLEditUtils::IsListItem(mEmptyInclusiveAncestorBlockElement));
5002 // If the found empty block is a list item element and its grand parent
5003 // (i.e., parent of list element) is NOT a list element, insert <br>
5004 // element before the list element which has the empty list item.
5005 // This odd list structure may occur if `Document.execCommand("indent")`
5006 // is performed for list items.
5007 // XXX Chrome does not remove empty list elements when last content in
5008 // last list item is deleted. We should follow it since current
5009 // behavior is annoying when you type new list item with selecting
5010 // all list items.
5011 if (!aHTMLEditor.IsFirstEditableChild(mEmptyInclusiveAncestorBlockElement)) {
5012 return RefPtr<Element>();
5015 EditorDOMPoint atParentOfEmptyListItem(
5016 mEmptyInclusiveAncestorBlockElement->GetParentElement());
5017 if (NS_WARN_IF(!atParentOfEmptyListItem.IsSet())) {
5018 return Err(NS_ERROR_FAILURE);
5020 if (HTMLEditUtils::IsAnyListElement(atParentOfEmptyListItem.GetContainer())) {
5021 return RefPtr<Element>();
5023 RefPtr<Element> brElement =
5024 aHTMLEditor.InsertBRElementWithTransaction(atParentOfEmptyListItem);
5025 if (NS_WARN_IF(aHTMLEditor.Destroyed())) {
5026 return Err(NS_ERROR_EDITOR_DESTROYED);
5028 if (!brElement) {
5029 NS_WARNING("HTMLEditor::InsertBRElementWithTransaction() failed");
5030 return Err(NS_ERROR_FAILURE);
5032 return brElement;
5035 Result<EditorDOMPoint, nsresult> HTMLEditor::AutoDeleteRangesHandler::
5036 AutoEmptyBlockAncestorDeleter::GetNewCaretPoisition(
5037 const HTMLEditor& aHTMLEditor,
5038 nsIEditor::EDirection aDirectionAndAmount) const {
5039 MOZ_ASSERT(mEmptyInclusiveAncestorBlockElement);
5040 MOZ_ASSERT(mEmptyInclusiveAncestorBlockElement->GetParentElement());
5041 MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable());
5043 switch (aDirectionAndAmount) {
5044 case nsIEditor::eNext:
5045 case nsIEditor::eNextWord:
5046 case nsIEditor::eToEndOfLine: {
5047 // Collapse Selection to next node of after empty block element
5048 // if there is. Otherwise, to just after the empty block.
5049 EditorDOMPoint afterEmptyBlock(
5050 EditorRawDOMPoint::After(mEmptyInclusiveAncestorBlockElement));
5051 MOZ_ASSERT(afterEmptyBlock.IsSet());
5052 if (nsIContent* nextContentOfEmptyBlock =
5053 aHTMLEditor.GetNextNode(afterEmptyBlock)) {
5054 EditorDOMPoint pt = aHTMLEditor.GetGoodCaretPointFor(
5055 *nextContentOfEmptyBlock, aDirectionAndAmount);
5056 if (!pt.IsSet()) {
5057 NS_WARNING("HTMLEditor::GetGoodCaretPointFor() failed");
5058 return Err(NS_ERROR_FAILURE);
5060 return pt;
5062 if (NS_WARN_IF(!afterEmptyBlock.IsSet())) {
5063 return Err(NS_ERROR_FAILURE);
5065 return afterEmptyBlock;
5067 case nsIEditor::ePrevious:
5068 case nsIEditor::ePreviousWord:
5069 case nsIEditor::eToBeginningOfLine: {
5070 // Collapse Selection to previous editable node of the empty block
5071 // if there is. Otherwise, to after the empty block.
5072 EditorRawDOMPoint atEmptyBlock(mEmptyInclusiveAncestorBlockElement);
5073 if (nsIContent* previousContentOfEmptyBlock =
5074 aHTMLEditor.GetPreviousEditableNode(atEmptyBlock)) {
5075 EditorDOMPoint pt = aHTMLEditor.GetGoodCaretPointFor(
5076 *previousContentOfEmptyBlock, aDirectionAndAmount);
5077 if (!pt.IsSet()) {
5078 NS_WARNING("HTMLEditor::GetGoodCaretPointFor() failed");
5079 return Err(NS_ERROR_FAILURE);
5081 return pt;
5083 EditorDOMPoint afterEmptyBlock(
5084 EditorRawDOMPoint::After(*mEmptyInclusiveAncestorBlockElement));
5085 if (NS_WARN_IF(!afterEmptyBlock.IsSet())) {
5086 return Err(NS_ERROR_FAILURE);
5088 return afterEmptyBlock;
5090 case nsIEditor::eNone:
5091 return EditorDOMPoint();
5092 default:
5093 MOZ_CRASH(
5094 "AutoEmptyBlockAncestorDeleter doesn't support this action yet");
5095 return EditorDOMPoint();
5099 EditActionResult
5100 HTMLEditor::AutoDeleteRangesHandler::AutoEmptyBlockAncestorDeleter::Run(
5101 HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount) {
5102 MOZ_ASSERT(mEmptyInclusiveAncestorBlockElement);
5103 MOZ_ASSERT(mEmptyInclusiveAncestorBlockElement->GetParentElement());
5104 MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable());
5106 if (HTMLEditUtils::IsListItem(mEmptyInclusiveAncestorBlockElement)) {
5107 Result<RefPtr<Element>, nsresult> result =
5108 MaybeInsertBRElementBeforeEmptyListItemElement(aHTMLEditor);
5109 if (result.isErr()) {
5110 NS_WARNING(
5111 "AutoEmptyBlockAncestorDeleter::"
5112 "MaybeInsertBRElementBeforeEmptyListItemElement() failed");
5113 return EditActionResult(result.inspectErr());
5115 // If a `<br>` element is inserted, caret should be moved to after it.
5116 if (RefPtr<Element> brElement = result.unwrap()) {
5117 nsresult rv =
5118 aHTMLEditor.CollapseSelectionTo(EditorRawDOMPoint(brElement));
5119 if (NS_FAILED(rv)) {
5120 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
5121 "HTMLEditor::CollapseSelectionTo() failed");
5122 return EditActionResult(rv);
5125 } else {
5126 Result<EditorDOMPoint, nsresult> result =
5127 GetNewCaretPoisition(aHTMLEditor, aDirectionAndAmount);
5128 if (result.isErr()) {
5129 NS_WARNING(
5130 "AutoEmptyBlockAncestorDeleter::GetNewCaretPoisition() failed");
5131 return EditActionResult(result.inspectErr());
5133 if (result.inspect().IsSet()) {
5134 nsresult rv = aHTMLEditor.CollapseSelectionTo(result.inspect());
5135 if (NS_FAILED(rv)) {
5136 NS_WARNING("HTMLEditor::CollapseSelectionTo() failed");
5137 return EditActionResult(rv);
5141 nsresult rv = aHTMLEditor.DeleteNodeWithTransaction(
5142 MOZ_KnownLive(*mEmptyInclusiveAncestorBlockElement));
5143 if (NS_WARN_IF(aHTMLEditor.Destroyed())) {
5144 return EditActionResult(NS_ERROR_EDITOR_DESTROYED);
5146 if (NS_FAILED(rv)) {
5147 NS_WARNING("HTMLEditor::DeleteNodeWithTransaction() failed");
5148 return EditActionResult(rv);
5150 return EditActionHandled();
5153 bool HTMLEditor::AutoDeleteRangesHandler::ExtendRangeToIncludeInvisibleNodes(
5154 const HTMLEditor& aHTMLEditor, const nsFrameSelection* aFrameSelection,
5155 nsRange& aRange) const {
5156 MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable());
5157 MOZ_ASSERT(!aRange.Collapsed());
5158 MOZ_ASSERT(aRange.IsPositioned());
5159 MOZ_ASSERT(!aRange.IsInSelection());
5161 EditorRawDOMPoint atStart(aRange.StartRef());
5162 EditorRawDOMPoint atEnd(aRange.EndRef());
5164 if (NS_WARN_IF(!aRange.GetClosestCommonInclusiveAncestor()->IsContent())) {
5165 return false;
5168 // Find current selection common block parent
5169 Element* commonAncestorBlock =
5170 HTMLEditUtils::GetInclusiveAncestorBlockElement(
5171 *aRange.GetClosestCommonInclusiveAncestor()->AsContent());
5172 if (NS_WARN_IF(!commonAncestorBlock)) {
5173 return false;
5176 // Set up for loops and cache our root element
5177 Element* editingHost = aHTMLEditor.GetActiveEditingHost();
5178 if (NS_WARN_IF(!editingHost)) {
5179 return false;
5182 // Find previous visible things before start of selection
5183 if (atStart.GetContainer() != commonAncestorBlock &&
5184 atStart.GetContainer() != editingHost) {
5185 for (;;) {
5186 WSScanResult backwardScanFromStartResult =
5187 WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary(aHTMLEditor,
5188 atStart);
5189 if (!backwardScanFromStartResult.ReachedCurrentBlockBoundary()) {
5190 break;
5192 MOZ_ASSERT(backwardScanFromStartResult.GetContent() ==
5193 WSRunScanner(aHTMLEditor, atStart).GetStartReasonContent());
5194 // We want to keep looking up. But stop if we are crossing table
5195 // element boundaries, or if we hit the root.
5196 if (HTMLEditUtils::IsAnyTableElement(
5197 backwardScanFromStartResult.GetContent()) ||
5198 backwardScanFromStartResult.GetContent() == commonAncestorBlock ||
5199 backwardScanFromStartResult.GetContent() == editingHost) {
5200 break;
5202 atStart = backwardScanFromStartResult.PointAtContent();
5204 if (aFrameSelection &&
5205 !aFrameSelection->IsValidSelectionPoint(atStart.GetContainer())) {
5206 NS_WARNING("Computed start container was out of selection limiter");
5207 return false;
5211 // Expand selection endpoint only if we don't pass an invisible `<br>`, or if
5212 // we really needed to pass that `<br>` (i.e., its block is now totally
5213 // selected).
5215 // Find next visible things after end of selection
5216 if (atEnd.GetContainer() != commonAncestorBlock &&
5217 atEnd.GetContainer() != editingHost) {
5218 EditorDOMPoint atFirstInvisibleBRElement;
5219 for (;;) {
5220 WSRunScanner wsScannerAtEnd(aHTMLEditor, atEnd);
5221 WSScanResult forwardScanFromEndResult =
5222 wsScannerAtEnd.ScanNextVisibleNodeOrBlockBoundaryFrom(atEnd);
5223 if (forwardScanFromEndResult.ReachedBRElement()) {
5224 // XXX In my understanding, this is odd. The end reason may not be
5225 // same as the reached <br> element because the equality is
5226 // guaranteed only when ReachedCurrentBlockBoundary() returns true.
5227 // However, looks like that this code assumes that
5228 // GetEndReasonContent() returns the (or a) <br> element.
5229 NS_ASSERTION(wsScannerAtEnd.GetEndReasonContent() ==
5230 forwardScanFromEndResult.BRElementPtr(),
5231 "End reason is not the reached <br> element");
5232 if (aHTMLEditor.IsVisibleBRElement(
5233 wsScannerAtEnd.GetEndReasonContent())) {
5234 break;
5236 if (!atFirstInvisibleBRElement.IsSet()) {
5237 atFirstInvisibleBRElement = atEnd;
5239 atEnd.SetAfter(wsScannerAtEnd.GetEndReasonContent());
5240 continue;
5243 if (forwardScanFromEndResult.ReachedCurrentBlockBoundary()) {
5244 MOZ_ASSERT(forwardScanFromEndResult.GetContent() ==
5245 wsScannerAtEnd.GetEndReasonContent());
5246 // We want to keep looking up. But stop if we are crossing table
5247 // element boundaries, or if we hit the root.
5248 if (HTMLEditUtils::IsAnyTableElement(
5249 forwardScanFromEndResult.GetContent()) ||
5250 forwardScanFromEndResult.GetContent() == commonAncestorBlock ||
5251 forwardScanFromEndResult.GetContent() == editingHost) {
5252 break;
5254 atEnd = forwardScanFromEndResult.PointAfterContent();
5255 continue;
5258 break;
5261 if (aFrameSelection &&
5262 !aFrameSelection->IsValidSelectionPoint(atEnd.GetContainer())) {
5263 NS_WARNING("Computed end container was out of selection limiter");
5264 return false;
5267 if (atFirstInvisibleBRElement.IsInContentNode()) {
5268 // Find block node containing invisible `<br>` element.
5269 if (RefPtr<Element> brElementParent =
5270 HTMLEditUtils::GetInclusiveAncestorBlockElement(
5271 *atFirstInvisibleBRElement.ContainerAsContent())) {
5272 EditorRawDOMRange range(atStart, atEnd);
5273 if (range.Contains(EditorRawDOMPoint(brElementParent))) {
5274 nsresult rv = aRange.SetStartAndEnd(atStart.ToRawRangeBoundary(),
5275 atEnd.ToRawRangeBoundary());
5276 if (NS_FAILED(rv)) {
5277 NS_WARNING("nsRange::SetStartAndEnd() failed to extend the range");
5278 return false;
5280 return aRange.IsPositioned() && aRange.StartRef().IsSet() &&
5281 aRange.EndRef().IsSet();
5283 // Otherwise, the new range should end at the invisible `<br>`.
5284 if (aFrameSelection && !aFrameSelection->IsValidSelectionPoint(
5285 atFirstInvisibleBRElement.GetContainer())) {
5286 NS_WARNING(
5287 "Computed end container (`<br>` element) was out of selection "
5288 "limiter");
5289 return false;
5291 atEnd = atFirstInvisibleBRElement;
5296 // XXX This is unnecessary creation cost for us since we just want to return
5297 // the start point and the end point.
5298 nsresult rv = aRange.SetStartAndEnd(atStart.ToRawRangeBoundary(),
5299 atEnd.ToRawRangeBoundary());
5300 if (NS_FAILED(rv)) {
5301 NS_WARNING("nsRange::SetStartAndEnd() failed to extend the range");
5302 return false;
5304 return aRange.IsPositioned() && aRange.StartRef().IsSet() &&
5305 aRange.EndRef().IsSet();
5308 } // namespace mozilla