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"
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"
31 #include "nsContentUtils.h"
34 #include "nsFrameSelection.h"
35 #include "nsGkAtoms.h"
36 #include "nsIContent.h"
40 #include "nsStringFwd.h"
43 // NOTE: This file was split from:
44 // https://searchfox.org/mozilla-central/rev/c409dd9235c133ab41eba635f906aa16e050c197/editor/libeditor/HTMLEditSubActionHandler.cpp
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
{
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()) {
81 Element
* root
= aHTMLEditor
.FindSelectionRoot(&aStartPointNode
);
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.
88 aRanges
->Initialize(aSelection
);
93 ~AutoSetTemporaryAncestorLimiter() {
95 mSelection
->SetAncestorLimiter(nullptr);
100 RefPtr
<Selection
> mSelection
;
103 /*****************************************************************************
104 * AutoDeleteRangesHandler
105 ****************************************************************************/
107 class MOZ_STACK_CLASS
HTMLEditor::AutoDeleteRangesHandler final
{
109 explicit AutoDeleteRangesHandler(
110 const AutoDeleteRangesHandler
* aParent
= nullptr)
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
128 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT EditActionResult
129 Run(HTMLEditor
& aHTMLEditor
, nsIEditor::EDirection aDirectionAndAmount
,
130 nsIEditor::EStripWrappers aStripWrappers
,
131 AutoRangeArray
& aRangesToDelete
);
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
154 * @param aWSRunScannerAtCaret Scanner instance which scanned from
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
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
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
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
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>`
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
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
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
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
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
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");
418 class MOZ_STACK_CLASS AutoBlockElementsJoiner final
{
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
440 * @return true if can continue to handle the
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
459 * @param aWSRunScannerAtCaret WSRunScanner instance which was
460 * initialized with the caret point.
461 * @return true if can continue to handle the
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
478 * @return true if can continue to handle the
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
492 * @param aRangesToDelete Ranges to delete of the caller.
493 * This should be collapsed and match
496 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT EditActionResult
497 Run(HTMLEditor
& aHTMLEditor
, nsIEditor::EDirection aDirectionAndAmount
,
498 nsIEditor::EStripWrappers aStripWrappers
,
499 const EditorDOMPoint
& aCaretPoint
, AutoRangeArray
& aRangesToDelete
) {
501 case Mode::JoinCurrentBlock
: {
502 EditActionResult result
=
503 HandleDeleteAtCurrentBlockBoundary(aHTMLEditor
, aCaretPoint
);
504 NS_WARNING_ASSERTION(result
.Succeeded(),
505 "AutoBlockElementsJoiner::"
506 "HandleDeleteAtCurrentBlockBoundary() failed");
509 case Mode::JoinOtherBlock
: {
510 EditActionResult result
= HandleDeleteAtOtherBlockBoundary(
511 aHTMLEditor
, aDirectionAndAmount
, aStripWrappers
, aCaretPoint
,
513 NS_WARNING_ASSERTION(result
.Succeeded(),
514 "AutoBlockElementsJoiner::"
515 "HandleDeleteAtOtherBlockBoundary() failed");
518 case Mode::DeleteBRElement
: {
519 EditActionResult result
=
520 DeleteBRElement(aHTMLEditor
, aDirectionAndAmount
, aCaretPoint
);
521 NS_WARNING_ASSERTION(
523 "AutoBlockElementsJoiner::DeleteBRElement() failed");
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 {
543 case Mode::JoinCurrentBlock
: {
544 nsresult rv
= ComputeRangesToDeleteAtCurrentBlockBoundary(
545 aHTMLEditor
, aCaretPoint
, aRangesToDelete
);
546 NS_WARNING_ASSERTION(
548 "AutoBlockElementsJoiner::"
549 "ComputeRangesToDeleteAtCurrentBlockBoundary() failed");
552 case Mode::JoinOtherBlock
: {
553 nsresult rv
= ComputeRangesToDeleteAtOtherBlockBoundary(
554 aHTMLEditor
, aDirectionAndAmount
, aCaretPoint
, aRangesToDelete
);
555 NS_WARNING_ASSERTION(
557 "AutoBlockElementsJoiner::"
558 "ComputeRangesToDeleteAtOtherBlockBoundary() failed");
561 case Mode::DeleteBRElement
: {
562 nsresult rv
= ComputeRangesToDeleteBRElement(aRangesToDelete
);
563 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
564 "AutoBlockElementsJoiner::"
565 "ComputeRangesToDeleteBRElement() failed");
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
:
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
588 * @param aRangesToDelete Ranges to delete. Must not be
590 * @param aSelectionWasCollapsed Whether selection was or was not
591 * collapsed when starting to handle
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
) {
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");
615 case Mode::DeleteContentInRanges
: {
616 EditActionResult result
=
617 DeleteContentInRanges(aHTMLEditor
, aDirectionAndAmount
,
618 aStripWrappers
, aRangesToDelete
);
619 NS_WARNING_ASSERTION(
621 "AutoBlockElementsJoiner::DeleteContentInRanges() failed");
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");
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
)
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(
660 "AutoBlockElementsJoiner::"
661 "ComputeRangesToJoinBlockElementsInSameParent() failed");
664 case Mode::DeleteContentInRanges
: {
665 nsresult rv
= ComputeRangesToDeleteContentInRanges(
666 aHTMLEditor
, aDirectionAndAmount
, aRangesToDelete
);
667 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
668 "AutoBlockElementsJoiner::"
669 "ComputeRangesToDeleteContentInRanges() failed");
672 case Mode::DeleteNonCollapsedRanges
: {
673 nsresult rv
= ComputeRangesToDeleteNonCollapsedRanges(
674 aHTMLEditor
, aDirectionAndAmount
, aRangesToDelete
,
675 aSelectionWasCollapsed
);
676 NS_WARNING_ASSERTION(
678 "AutoBlockElementsJoiner::"
679 "ComputeRangesToDeleteNonCollapsedRanges() failed");
682 case Mode::NotInitialized
:
683 MOZ_ASSERT_UNREACHABLE(
684 "Call ComputeRangesToDelete() after calling a preparation "
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
;
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
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
)
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
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
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
)
791 Result
<bool, nsresult
>
792 ComputeRangesToDeleteNodesEntirelyInRangeButKeepTableStructure(
793 const HTMLEditor
& aHTMLEditor
, nsRange
& aRange
,
794 AutoDeleteRangesHandler::SelectionWasCollapsed aSelectionWasCollapsed
)
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
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
{
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
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
877 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT EditActionResult
878 Run(HTMLEditor
& aHTMLEditor
);
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 {
892 // `MergeFirstLineOfRightBlockElementIntoDescendantLeftBlockElement()`
893 if (mPointContainingTheOtherBlockElement
.GetContainer() ==
894 mRightBlockElement
) {
895 return mNewListElementTagNameOfRightListElement
.isSome();
897 // `MergeFirstLineOfRightBlockElementIntoAncestorLeftBlockElement()`
898 if (mPointContainingTheOtherBlockElement
.GetContainer() ==
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
;
918 bool mFallbackToDeleteLeafContent
;
919 }; // HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
920 // AutoInclusiveAncestorBlockElementsJoiner
926 JoinBlocksInSameParent
,
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
{
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
979 * - If ePrevious, ePreviousWord or
980 * eToBeginningOfLine, collapse Selection to
981 * end of previous editable node.
982 * - Otherwise, eNone is allowed but does
985 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT EditActionResult
986 Run(HTMLEditor
& aHTMLEditor
, nsIEditor::EDirection aDirectionAndAmount
);
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()) {
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
);
1040 AutoDeleteRangesHandler deleteHandler
;
1041 nsresult rv
= deleteHandler
.ComputeRangesToDelete(*this, aDirectionAndAmount
,
1043 NS_WARNING_ASSERTION(
1045 "AutoDeleteRangesHandler::ComputeRangesToDelete() failed");
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
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(
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");
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
)) {
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");
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(
1148 "AutoEmptyBlockAncestorDeleter::ComputeTargetRanges() failed");
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
,
1157 if (bidiLevelManager
.Failed()) {
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(),
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
,
1190 if (shrunkenResult
.isErr()) {
1192 "AutoRangeArray::ShrinkRangesIfStartFromOrEndAfterAtomicContent() "
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(
1211 "AutoDeleteRangesHandler::"
1212 "FallbackToComputeRangesToDeleteRangesWithTransaction() failed");
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(
1230 : wsRunScannerAtCaret
.ScanPreviousVisibleNodeOrBlockBoundaryFrom(
1232 if (scanFromCaretPointResult
.Failed()) {
1234 "WSRunScanner::Scan(Next|Previous)VisibleNodeOrBlockBoundaryFrom() "
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()) {
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(
1277 "Recursive AutoDeleteRangesHandler::ComputeRangesToDelete() "
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");
1298 // Otherwise, extend the range to contain the invisible `<br>`
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");
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");
1322 NS_WARNING("Was the invisible `<br>` element selected?");
1327 nsresult rv
= ComputeRangesToDeleteAroundCollapsedRanges(
1328 aHTMLEditor
, aDirectionAndAmount
, aRangesToDelete
,
1329 wsRunScannerAtCaret
, scanFromCaretPointResult
);
1330 NS_WARNING_ASSERTION(
1332 "AutoDeleteRangesHandler::ComputeRangesToDeleteAroundCollapsedRanges("
1338 nsresult rv
= ComputeRangesToDeleteNonCollapsedRanges(
1339 aHTMLEditor
, aDirectionAndAmount
, aRangesToDelete
, selectionWasCollapsed
);
1340 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
1341 "AutoDeleteRangesHandler::"
1342 "ComputeRangesToDeleteNonCollapsedRanges() failed");
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
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
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
);
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");
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
,
1415 if (bidiLevelManager
.Failed()) {
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(),
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
1463 Result
<bool, nsresult
> shrunkenResult
=
1464 aRangesToDelete
.ShrinkRangesIfStartFromOrEndAfterAtomicContent(
1465 aHTMLEditor
, aDirectionAndAmount
,
1466 AutoRangeArray::IfSelectingOnlyOneAtomicContent::Collapse
,
1468 if (shrunkenResult
.isErr()) {
1470 "AutoRangeArray::ShrinkRangesIfStartFromOrEndAfterAtomicContent() "
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");
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(
1505 : wsRunScannerAtCaret
.ScanPreviousVisibleNodeOrBlockBoundaryFrom(
1507 if (scanFromCaretPointResult
.Failed()) {
1509 "WSRunScanner::Scan(Next|Previous)VisibleNodeOrBlockBoundaryFrom() "
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(
1535 MOZ_KnownLive(*scanFromCaretPointResult
.BRElementPtr()),
1537 if (NS_FAILED(rv
)) {
1539 "WhiteSpaceVisibilityKeeper::"
1540 "DeleteContentNodeAndJoinTextNodesAroundIt() failed");
1541 return EditActionHandled(rv
);
1543 if (aHTMLEditor
.SelectionRefPtr()->RangeCount() != 1) {
1545 "Selection was unexpected after removing an invisible `<br>` "
1547 return EditActionHandled(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
);
1549 AutoRangeArray
rangesToDelete(*aHTMLEditor
.SelectionRefPtr());
1550 caretPoint
= Some(aRangesToDelete
.GetStartPointOfFirstRange());
1551 if (!caretPoint
.ref().IsSet()) {
1553 "New selection after deleting invisible `<br>` element was "
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(
1569 : wsRunScannerAtCaret
1570 .ScanPreviousVisibleNodeOrBlockBoundaryFrom(
1572 if (scanFromCaretPointResult
.Failed()) {
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(
1589 "Recursive AutoDeleteRangesHandler::Run() failed");
1594 EditActionResult result
= HandleDeleteAroundCollapsedRanges(
1595 aHTMLEditor
, aDirectionAndAmount
, aStripWrappers
, aRangesToDelete
,
1596 wsRunScannerAtCaret
, scanFromCaretPointResult
);
1597 NS_WARNING_ASSERTION(result
.Succeeded(),
1598 "AutoDeleteRangesHandler::"
1599 "HandleDeleteAroundCollapsedRanges() failed");
1604 EditActionResult result
= HandleDeleteNonCollapsedRanges(
1605 aHTMLEditor
, aDirectionAndAmount
, aStripWrappers
, aRangesToDelete
,
1606 selectionWasCollapsed
);
1607 NS_WARNING_ASSERTION(
1609 "AutoDeleteRangesHandler::HandleDeleteNonCollapsedRanges() failed");
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(
1629 "AutoDeleteRangesHandler::"
1630 "ComputeRangesToDeleteTextAroundCollapsedRanges() failed");
1634 if (aScanFromCaretPointResult
.ReachedSpecialContent() ||
1635 aScanFromCaretPointResult
.ReachedBRElement()) {
1636 if (aScanFromCaretPointResult
.GetContent() ==
1637 aWSRunScannerAtCaret
.GetEditingHost()) {
1640 nsresult rv
= ComputeRangesToDeleteAtomicContent(
1641 aHTMLEditor
, *aScanFromCaretPointResult
.GetContent(), aRangesToDelete
);
1642 NS_WARNING_ASSERTION(
1644 "AutoDeleteRangesHandler::ComputeRangesToDeleteAtomicContent() failed");
1648 if (aScanFromCaretPointResult
.ReachedHRElement()) {
1649 if (aScanFromCaretPointResult
.GetContent() ==
1650 aWSRunScannerAtCaret
.GetEditingHost()) {
1654 ComputeRangesToDeleteHRElement(aHTMLEditor
, aDirectionAndAmount
,
1655 *aScanFromCaretPointResult
.ElementPtr(),
1656 aWSRunScannerAtCaret
.ScanStartRef(),
1657 aWSRunScannerAtCaret
, aRangesToDelete
);
1658 NS_WARNING_ASSERTION(
1660 "AutoDeleteRangesHandler::ComputeRangesToDeleteHRElement() failed");
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(),
1678 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
1679 "AutoBlockElementsJoiner::ComputeRangesToDelete() "
1680 "failed (other block boundary)");
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(),
1698 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
1699 "AutoBlockElementsJoiner::ComputeRangesToDelete() "
1700 "failed (current block boundary)");
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(),
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");
1738 if (aScanFromCaretPointResult
.InNormalWhiteSpaces()) {
1739 EditActionResult result
= HandleDeleteCollapsedSelectionAtWhiteSpaces(
1740 aHTMLEditor
, aDirectionAndAmount
, aWSRunScannerAtCaret
.ScanStartRef());
1741 NS_WARNING_ASSERTION(result
.Succeeded(),
1742 "AutoDeleteRangesHandler::"
1743 "HandleDelectCollapsedSelectionAtWhiteSpaces() "
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() "
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(
1772 "AutoDeleteRangesHandler::HandleDeleteAtomicContent() failed");
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(
1787 "AutoDeleteRangesHandler::HandleDeleteHRElement() failed");
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(
1807 "AutoBlockElementsJoiner::Run() failed (other block boundary)");
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(
1827 "AutoBlockElementsJoiner::Run() failed (current block boundary)");
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
,
1855 if (result
.isErr()) {
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.
1865 Result
<EditorDOMRangeInTexts
, nsresult
> result
=
1866 WSRunScanner::GetRangeInTextNodesToBackspaceFrom(aHTMLEditor
,
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");
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()) {
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
)) {
1950 "WhiteSpaceVisibilityKeeper::DeleteInclusiveNextWhiteSpace() failed");
1951 return EditActionHandled(rv
);
1954 nsresult rv
= WhiteSpaceVisibilityKeeper::DeletePreviousWhiteSpace(
1955 aHTMLEditor
, aPointToDelete
);
1956 if (NS_FAILED(rv
)) {
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
);
1969 aHTMLEditor
.InsertBRElementIfHardLineIsEmptyAndEndsWithBlockBoundary(
1971 NS_WARNING_ASSERTION(
1973 "HTMLEditor::InsertBRElementIfHardLineIsEmptyAndEndsWithBlockBoundary() "
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();
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
)) {
2021 "WhiteSpaceVisibilityKeeper::PrepareToDeleteRangeAndTrackPoints() "
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
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(
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(
2086 if (NS_FAILED(rv
)) {
2088 "HTMLEditor::InsertBRElementIfHardLineIsEmptyAndEndsWithBlockBoundary()"
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()) {
2110 if (aDirectionAndAmount
!= nsIEditor::ePrevious
) {
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
2124 // aCaretPoint's offset -1 == `<hr>` element offset
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
);
2134 bool interLineIsRight
=
2135 aHTMLEditor
.SelectionRefPtr()->GetInterlinePosition(error
);
2136 if (error
.Failed()) {
2137 NS_WARNING("Selection::GetInterlinePosition() failed");
2138 nsresult rv
= error
.StealNSResult();
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
,
2165 NS_WARNING_ASSERTION(
2167 "AutoDeleteRangesHandler::ComputeRangesToDeleteAtomicContent() failed");
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");
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(
2190 "AutoDeleteRangesHandler::ComputeRangesToDeleteAtomicContent() failed");
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(
2213 "AutoDeleteRangesHandler::HandleDeleteAtomicContent() failed");
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(
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();
2257 WhiteSpaceVisibilityKeeper::DeleteContentNodeAndJoinTextNodesAroundIt(
2259 MOZ_KnownLive(*forwardScanFromCaretResult
.BRElementPtr()),
2261 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
2262 "WhiteSpaceVisibilityKeeper::"
2263 "DeleteContentNodeAndJoinTextNodesAroundIt() failed");
2264 return EditActionHandled(rv
);
2268 HTMLEditor::AutoDeleteRangesHandler::ComputeRangesToDeleteAtomicContent(
2269 const HTMLEditor
& aHTMLEditor
, const nsIContent
& aAtomicContent
,
2270 AutoRangeArray
& aRangesToDelete
) const {
2271 EditorDOMRange rangeToDelete
=
2272 WSRunScanner::GetRangesForDeletingAtomicContent(aHTMLEditor
,
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");
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());
2295 WhiteSpaceVisibilityKeeper::DeleteContentNodeAndJoinTextNodesAroundIt(
2296 aHTMLEditor
, aAtomicContent
, aCaretPoint
);
2297 if (NS_WARN_IF(NS_FAILED(rv
))) {
2299 "WhiteSpaceVisibilityKeeper::DeleteContentNodeAndJoinTextNodesAroundIt("
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(
2313 NS_WARNING_ASSERTION(
2315 "HTMLEditor::InsertBRElementIfHardLineIsEmptyAndEndsWithBlockBoundary() "
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
)) {
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();
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(
2357 : aWSRunScannerAtCaret
.ScanNextVisibleNodeOrBlockBoundaryFrom(
2359 // If we found a `<br>` element, we need to delete it instead of joining the
2361 if (scanFromCaretResult
.ReachedBRElement()) {
2362 mBRElement
= scanFromCaretResult
.BRElementPtr();
2363 mMode
= Mode::DeleteBRElement
;
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
2375 nsresult rv
= aRangesToDelete
.SelectNode(*mBRElement
);
2376 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
), "AutoRangeArray::SelectNode() failed");
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
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
,
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
,
2439 if (!mDeleteRangesHandlerConst
.CanFallbackToDeleteRangesWithTransaction(
2441 nsresult rv
= aRangesToDelete
.Collapse(aCaretPoint
);
2442 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
2443 "AutoRangeArray::Collapse() failed");
2446 nsresult rv
= mDeleteRangesHandlerConst
2447 .FallbackToComputeRangesToDeleteRangesWithTransaction(
2448 aHTMLEditor
, aRangesToDelete
);
2449 NS_WARNING_ASSERTION(
2451 "AutoDeleteRangesHandler::"
2452 "FallbackToComputeRangesToDeleteRangesWithTransaction() failed");
2456 AutoInclusiveAncestorBlockElementsJoiner
joiner(*mLeftContent
,
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()) {
2466 joiner
.ComputeRangesToDelete(aHTMLEditor
, aCaretPoint
, aRangesToDelete
);
2467 NS_WARNING_ASSERTION(
2469 "AutoInclusiveAncestorBlockElementsJoiner::ComputeRangesToDelete() "
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()) {
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
2493 if (aRangesToDelete
.IsCollapsed() &&
2494 aRangesToDelete
.FocusRef() == newCaretPoint
.ToRawRangeBoundary()) {
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
,
2506 NS_WARNING_ASSERTION(
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
)
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
,
2535 // If we have not deleted `<br>` element and are not called recursively,
2536 // we should call `DeleteRangesWithTransaction()` here.
2537 if (!mDeleteRangesHandler
->CanFallbackToDeleteRangesWithTransaction(
2539 return EditActionIgnored();
2541 EditActionResult result
=
2542 mDeleteRangesHandler
->FallbackToDeleteRangesWithTransaction(
2543 aHTMLEditor
, aRangesToDelete
);
2544 NS_WARNING_ASSERTION(
2546 "AutoDeleteRangesHandler::FallbackToDeleteRangesWithTransaction() "
2547 "failed to delete leaf content in the block");
2551 // Else we are joining content to block
2552 AutoInclusiveAncestorBlockElementsJoiner
joiner(*mLeftContent
,
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(
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(),
2577 result
|= joiner
.Run(aHTMLEditor
);
2578 if (result
.Failed()) {
2579 NS_WARNING("AutoInclusiveAncestorBlockElementsJoiner::Run() failed");
2583 if (joiner
.ShouldDeleteLeafContentInstead()) {
2586 "Assumed `AutoInclusiveAncestorBlockElementsJoiner::Run()` "
2587 "retruning ignored, but returned not 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
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
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");
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");
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
2652 mMode
= Mode::JoinCurrentBlock
;
2654 // Don't break the basic structure of the HTML document.
2655 if (aCurrentBlockElement
.IsAnyOfHTMLElements(nsGkAtoms::html
, nsGkAtoms::head
,
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
)) {
2666 if (aDirectionAndAmount
== nsIEditor::ePrevious
) {
2668 aHTMLEditor
.GetPreviousEditableHTMLNode(aCurrentBlockElement
);
2669 mRightContent
= aCaretPoint
.GetContainerAsContent();
2671 mRightContent
= aHTMLEditor
.GetNextEditableHTMLNode(aCurrentBlockElement
);
2672 mLeftContent
= aCaretPoint
.GetContainerAsContent();
2676 if (!mLeftContent
|| !mRightContent
) {
2680 // Don't cross table boundaries.
2681 return !HTMLEditor::NodesInDifferentTableElements(*mLeftContent
,
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
,
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()) {
2701 joiner
.ComputeRangesToDelete(aHTMLEditor
, aCaretPoint
, aRangesToDelete
);
2702 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
2703 "AutoInclusiveAncestorBlockElementsJoiner::"
2704 "ComputeRangesToDelete() failed");
2708 // In this case, nothing will be deleted so that the affected range should
2710 nsresult rv
= aRangesToDelete
.Collapse(aCaretPoint
);
2711 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
), "AutoRangeArray::Collapse() failed");
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
,
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(
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");
2750 if (joiner
.ShouldDeleteLeafContentInstead()) {
2751 NS_ASSERTION(result
.Ignored(),
2752 "Assumed `AutoInclusiveAncestorBlockElementsJoiner::Run()` "
2753 "retruning ignored, but returned not ignored");
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");
2776 HTMLEditor::AutoDeleteRangesHandler::ComputeRangesToDeleteNonCollapsedRanges(
2777 const HTMLEditor
& aHTMLEditor
, nsIEditor::EDirection aDirectionAndAmount
,
2778 AutoRangeArray
& aRangesToDelete
,
2779 AutoDeleteRangesHandler::SelectionWasCollapsed aSelectionWasCollapsed
)
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())) {
2797 "AutoDeleteRangesHandler::ExtendRangeToIncludeInvisibleNodes() "
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(
2826 "AutoDeleteRangesHandler::ComputeRangesToDeleteRangesWithTransaction("
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.
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
;
2852 joiner
.ComputeRangesToDelete(aHTMLEditor
, aDirectionAndAmount
,
2853 aRangesToDelete
, aSelectionWasCollapsed
);
2854 NS_WARNING_ASSERTION(
2856 "AutoBlockElementsJoiner::ComputeRangesToDelete() failed");
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())) {
2884 "AutoDeleteRangesHandler::ExtendRangeToIncludeInvisibleNodes() "
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
)) {
2903 "WhiteSpaceVisibilityKeeper::PrepareToDeleteRange() "
2905 return EditActionResult(rv
);
2908 !aRangesToDelete
.FirstRangeRef()->StartRef().IsSetAndValid()) ||
2910 !aRangesToDelete
.FirstRangeRef()->EndRef().IsSetAndValid())) {
2912 "WhiteSpaceVisibilityKeeper::PrepareToDeleteRange() made the firstr "
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
);
2948 !aRangesToDelete
.FirstRangeRef()->GetStartContainer()->IsContent()) ||
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");
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
)) {
2994 if (mLeftContent
== mRightContent
) {
2995 mMode
= Mode::DeleteContentInRanges
;
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
;
3015 mMode
= Mode::DeleteNonCollapsedRanges
;
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()
3036 ->IsInclusiveDescendantOf(mRightContent
));
3039 mDeleteRangesHandlerConst
.ComputeRangesToDeleteRangesWithTransaction(
3040 aHTMLEditor
, aDirectionAndAmount
, aRangesToDelete
);
3041 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
3042 "AutoDeleteRangesHandler::"
3043 "ComputeRangesToDeleteRangesWithTransaction() failed");
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()
3065 ->IsInclusiveDescendantOf(mRightContent
));
3067 // XXX This is also odd. We do we simply use
3068 // `DeleteRangesWithTransaction()` only when **first** range is in
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
) {
3078 "EditorBase::DeleteRangesWithTransaction() caused destroying the "
3080 return EditActionHandled(NS_ERROR_EDITOR_DESTROYED
);
3082 NS_WARNING_ASSERTION(
3084 "EditorBase::DeleteRangesWithTransaction() failed, but ignored");
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()
3114 ->IsInclusiveDescendantOf(mRightContent
));
3115 MOZ_ASSERT(mLeftContent
->GetParentNode() == mRightContent
->GetParentNode());
3118 mDeleteRangesHandlerConst
.ComputeRangesToDeleteRangesWithTransaction(
3119 aHTMLEditor
, aDirectionAndAmount
, aRangesToDelete
);
3120 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
3121 "AutoDeleteRangesHandler::"
3122 "ComputeRangesToDeleteRangesWithTransaction() failed");
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()
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
)
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");
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");
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
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.
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(
3227 "AutoBlockElementsJoiner::DeleteContentButKeepTableStructure() failed, "
3230 return needsToJoinLater
;
3233 bool HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
3234 NeedsToJoinNodesAfterDeleteNodesEntirelyInRangeButKeepTableStructure(
3235 const HTMLEditor
& aHTMLEditor
,
3236 const nsTArray
<OwningNonNull
<nsIContent
>>& aArrayOfContents
,
3237 AutoDeleteRangesHandler::SelectionWasCollapsed aSelectionWasCollapsed
)
3239 // If original selection was collapsed, we need always to join the nodes.
3241 if (aSelectionWasCollapsed
==
3242 AutoDeleteRangesHandler::SelectionWasCollapsed::No
) {
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()) {
3250 for (const OwningNonNull
<nsIContent
>& content
: aArrayOfContents
) {
3251 if (content
->IsText()) {
3252 if (aHTMLEditor
.IsInVisibleTextFrames(*content
->AsText())) {
3257 // XXX If it's an element node, we should check whether it has visible
3259 if (!content
->IsElement() ||
3260 aHTMLEditor
.IsEmptyNode(*content
->AsElement())) {
3263 if (!content
->IsHTMLElement(nsGkAtoms::br
) ||
3264 aHTMLEditor
.IsVisibleBRElement(content
)) {
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");
3289 if (rangeEnd
.IsInTextNode() && !rangeEnd
.IsStartOfContainer()) {
3290 // Delete to first character
3291 OwningNonNull
<Text
> textNode
= *rangeEnd
.GetContainerAsText();
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");
3305 nsresult
HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
3306 ComputeRangesToDeleteNonCollapsedRanges(
3307 const HTMLEditor
& aHTMLEditor
,
3308 nsIEditor::EDirection aDirectionAndAmount
,
3309 AutoRangeArray
& aRangesToDelete
,
3310 AutoDeleteRangesHandler::SelectionWasCollapsed aSelectionWasCollapsed
)
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()
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()) {
3331 "AutoBlockElementsJoiner::"
3332 "ComputeRangesToDeleteNodesEntirelyInRangeButKeepTableStructure() "
3334 return result
.unwrapErr();
3336 if (!result
.unwrap()) {
3341 AutoInclusiveAncestorBlockElementsJoiner
joiner(*mLeftContent
,
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()) {
3357 nsresult rv
= joiner
.ComputeRangesToDelete(aHTMLEditor
, EditorDOMPoint(),
3359 NS_WARNING_ASSERTION(
3361 "AutoInclusiveAncestorBlockElementsJoiner::ComputeRangesToDelete() "
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()
3384 ->IsInclusiveDescendantOf(mRightContent
));
3386 // Otherwise, delete every nodes in all ranges, then, clean up something.
3387 EditActionResult
result(NS_OK
);
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()) {
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
)) {
3415 "AutoBlockElementsJoiner::DeleteTextAtStartAndEndOfRange() failed");
3416 return EditActionHandled(rv
);
3419 if (!joinInclusiveAncestorBlockElements
) {
3423 AutoInclusiveAncestorBlockElementsJoiner
joiner(*mLeftContent
,
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
3440 if (aDirectionAndAmount
== nsIEditor::eNext
) {
3441 aDirectionAndAmount
= nsIEditor::ePrevious
;
3443 aDirectionAndAmount
= nsIEditor::eNext
;
3446 if (!canJoinThem
.inspect()) {
3447 result
.MarkAsCanceled();
3451 if (!joiner
.CanJoinBlocks()) {
3455 result
|= joiner
.Run(aHTMLEditor
);
3456 if (result
.Failed()) {
3457 NS_WARNING("AutoInclusiveAncestorBlockElementsJoiner::Run() failed");
3461 if (joiner
.ShouldDeleteLeafContentInstead()) {
3462 NS_ASSERTION(result
.Ignored(),
3463 "Assumed `AutoInclusiveAncestorBlockElementsJoiner::Run()` "
3464 "retruning ignored, but returned not ignored");
3466 NS_ASSERTION(!result
.Ignored(),
3467 "Assumed `AutoInclusiveAncestorBlockElementsJoiner::Run()` "
3468 "retruning handled, but returned ignored");
3470 #endif // #ifdef DEBUG
3475 mDeleteRangesHandler
->DeleteUnnecessaryNodesAndCollapseSelection(
3476 aHTMLEditor
, aDirectionAndAmount
,
3477 EditorDOMPoint(aRangesToDelete
.FirstRangeRef()->StartRef()),
3478 EditorDOMPoint(aRangesToDelete
.FirstRangeRef()->EndRef()));
3479 if (NS_FAILED(rv
)) {
3481 "AutoDeleteRangesHandler::DeleteUnnecessaryNodesAndCollapseSelection() "
3483 return EditActionResult(rv
);
3486 return result
.MarkAsHandled();
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
);
3515 DeleteParentBlocksWithTransactionIfEmpty(aHTMLEditor
, atCaret
);
3516 if (NS_FAILED(rv
)) {
3518 "HTMLEditor::DeleteParentBlocksWithTransactionIfEmpty() failed");
3521 aHTMLEditor
.TopLevelEditSubActionDataRef().mDidDeleteEmptyParentBlocks
=
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");
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(
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(
3562 "AutoDeleteRangesHandler::DeleteNodeIfInvisibleAndEditableTextNode() "
3563 "failed to remove end node, but ignored");
3566 nsresult rv
= aHTMLEditor
.CollapseSelectionTo(
3567 aDirectionAndAmount
== nsIEditor::ePrevious
? selectionEndPoint
3569 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
3570 "HTMLEditor::CollapseSelectionTo() failed");
3575 HTMLEditor::AutoDeleteRangesHandler::DeleteNodeIfInvisibleAndEditableTextNode(
3576 HTMLEditor
& aHTMLEditor
, nsIContent
& aContent
) {
3577 MOZ_ASSERT(aHTMLEditor
.IsEditActionDataAvailable());
3579 Text
* text
= aContent
.GetAsText();
3584 if (aHTMLEditor
.IsVisibleTextNode(*text
) ||
3585 !HTMLEditUtils::IsSimplyEditableNode(*text
)) {
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");
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
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
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");
3681 // If we reach editing host, return NS_OK.
3682 if (nextPoint
.GetContainer() == wsScannerForPoint
.GetEditingHost()) {
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(),
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");
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()) {
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");
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()) {
3754 const nsTextFragment
& textFragment
=
3755 aCaretPoint
.ContainerAsText()->TextFragment();
3756 if (!textFragment
.GetLength()) {
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");
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()) {
3782 if (howToHandleCollapsedRange
==
3783 EditorBase::HowToHandleCollapsedRange::Ignore
) {
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
) {
3798 if (!previousEditableContent
->IsText()) {
3799 IgnoredErrorResult ignoredError
;
3800 range
->SelectNode(*previousEditableContent
, ignoredError
);
3801 NS_WARNING_ASSERTION(!ignoredError
.Failed(),
3802 "nsRange::SelectNode() failed");
3806 extendRangeToSelectCharacterBackward(
3808 EditorRawDOMPointInText::AtEndOf(*previousEditableContent
->AsText()));
3812 if (howToHandleCollapsedRange
==
3813 EditorBase::HowToHandleCollapsedRange::ExtendForward
&&
3814 caretPoint
.IsEndOfContainer()) {
3815 nsIContent
* nextEditableContent
=
3816 aHTMLEditor
.GetNextEditableNode(*caretPoint
.GetContainer());
3817 if (!nextEditableContent
) {
3821 if (!nextEditableContent
->IsText()) {
3822 IgnoredErrorResult ignoredError
;
3823 range
->SelectNode(*nextEditableContent
, ignoredError
);
3824 NS_WARNING_ASSERTION(!ignoredError
.Failed(),
3825 "nsRange::SelectNode() failed");
3829 extendRangeToSelectCharacterForward(
3830 range
, EditorRawDOMPointInText(nextEditableContent
->AsText(), 0));
3834 if (caretPoint
.IsInTextNode()) {
3835 if (howToHandleCollapsedRange
==
3836 EditorBase::HowToHandleCollapsedRange::ExtendBackward
) {
3837 extendRangeToSelectCharacterBackward(
3838 range
, EditorRawDOMPointInText(caretPoint
.ContainerAsText(),
3839 caretPoint
.Offset()));
3842 extendRangeToSelectCharacterForward(
3843 range
, EditorRawDOMPointInText(caretPoint
.ContainerAsText(),
3844 caretPoint
.Offset()));
3848 nsIContent
* editableContent
=
3849 howToHandleCollapsedRange
==
3850 EditorBase::HowToHandleCollapsedRange::ExtendBackward
3851 ? aHTMLEditor
.GetPreviousEditableNode(caretPoint
)
3852 : aHTMLEditor
.GetNextEditableNode(caretPoint
);
3853 if (!editableContent
) {
3856 while (editableContent
&& editableContent
->IsCharacterData() &&
3857 !editableContent
->Length()) {
3859 howToHandleCollapsedRange
==
3860 EditorBase::HowToHandleCollapsedRange::ExtendBackward
3861 ? aHTMLEditor
.GetPreviousEditableNode(*editableContent
)
3862 : aHTMLEditor
.GetNextEditableNode(*editableContent
);
3864 if (!editableContent
) {
3868 if (!editableContent
->IsText()) {
3869 IgnoredErrorResult ignoredError
;
3870 range
->SelectNode(*editableContent
, ignoredError
);
3871 NS_WARNING_ASSERTION(!ignoredError
.Failed(),
3872 "nsRange::SelectNode() failed");
3876 if (howToHandleCollapsedRange
==
3877 EditorBase::HowToHandleCollapsedRange::ExtendBackward
) {
3878 extendRangeToSelectCharacterBackward(
3879 range
, EditorRawDOMPointInText::AtEndOf(*editableContent
->AsText()));
3882 extendRangeToSelectCharacterForward(
3883 range
, EditorRawDOMPointInText(editableContent
->AsText(), 0));
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
3900 if (aStartPoint
== aEndPoint
) {
3901 // Nothing to delete
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");
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");
3939 RefPtr
<Text
> textNode
= aStartPoint
.ContainerAsText();
3941 DeleteTextWithTransaction(*textNode
, aStartPoint
.Offset(),
3942 aEndPoint
.Offset() - aStartPoint
.Offset());
3943 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
3944 "HTMLEditor::DeleteTextWithTransaction() failed");
3948 RefPtr
<nsRange
> range
=
3949 nsRange::Create(aStartPoint
.ToRawRangeBoundary(),
3950 aEndPoint
.ToRawRangeBoundary(), IgnoreErrors());
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
;
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
);
3968 for (OwningNonNull
<Text
>& textNode
: arrayOfTextNodes
) {
3969 if (textNode
== aStartPoint
.GetContainer()) {
3970 if (aStartPoint
.IsEndOfContainer()) {
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");
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");
3997 if (textNode
== aEndPoint
.GetContainer()) {
3998 if (aEndPoint
.IsStartOfContainer()) {
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");
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");
4021 deleteEmptyContentNodeWithTransaction(MOZ_KnownLive(textNode
));
4022 if (NS_FAILED(rv
)) {
4023 NS_WARNING("deleteEmptyContentNodeWithTransaction() failed");
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
4039 nsCOMPtr
<nsIContent
> leftContentToJoin
= &aLeftContent
;
4040 nsCOMPtr
<nsIContent
> rightContentToJoin
= &aRightContent
;
4041 nsCOMPtr
<nsINode
> parentNode
= aRightContent
.GetParentNode();
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();
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");
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!
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();
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
) {
4093 while (rightContentToJoin
&& !EditorUtils::IsEditableContent(
4094 *rightContentToJoin
, EditorType::HTML
)) {
4095 rightContentToJoin
= rightContentToJoin
->GetNextSibling();
4097 if (!rightContentToJoin
) {
4103 NS_WARNING("HTMLEditor::JoinNodesDeepWithTransaction() joined no contents");
4104 return Err(NS_ERROR_FAILURE
);
4109 Result
<bool, nsresult
> HTMLEditor::AutoDeleteRangesHandler::
4110 AutoBlockElementsJoiner::AutoInclusiveAncestorBlockElementsJoiner::Prepare(
4111 const HTMLEditor
& aHTMLEditor
) {
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
,
4128 mRightBlockElement
->IsAnyOfHTMLElements(nsGkAtoms::html
, nsGkAtoms::head
,
4130 mCanJoinBlocks
= false;
4134 if (HTMLEditUtils::IsAnyTableElement(mLeftBlockElement
) ||
4135 HTMLEditUtils::IsAnyTableElement(mRightBlockElement
)) {
4136 // Do not try to merge table elements, cancel the deletion.
4137 mCanJoinBlocks
= false;
4141 // Bail if both blocks the same
4142 if (IsSameBlockElement()) {
4143 mCanJoinBlocks
= true; // XXX Anyway, Run() will ingore this case.
4144 mFallbackToDeleteLeafContent
= 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;
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
,
4168 !EditorUtils::IsDescendantOf(*rightListElement
, *mLeftBlockElement
,
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;
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();
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
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();
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();
4248 // Marked as handled when deleting the invisible `<br>` element.
4249 mFallbackToDeleteLeafContent
= false;
4252 mPrecedingInvisibleBRElement
=
4253 WSRunScanner::GetPrecedingBRElementUnlessVisibleContentFound(
4254 aHTMLEditor
, EditorDOMPoint::AtEndOf(mLeftBlockElement
));
4255 // `WhiteSpaceVisibilityKeeper::
4256 // MergeFirstLineOfRightBlockElementIntoLeftBlockElement()` always
4257 // return "handled".
4258 mFallbackToDeleteLeafContent
= false;
4261 mCanJoinBlocks
= 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");
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()) {
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())
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");
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");
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
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");
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(
4403 "WhiteSpaceVisibilityKeeper::"
4404 "MergeFirstLineOfRightBlockElementIntoLeftBlockElement() failed");
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()) {
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)) {
4439 nsINode
* commonAncestor
= oneLineRange
->GetClosestCommonInclusiveAncestor();
4440 // Currently, we move non-editable content nodes too.
4441 EditorRawDOMPoint
startPoint(oneLineRange
->StartRef());
4442 if (!startPoint
.IsEndOfContainer()) {
4445 EditorRawDOMPoint
endPoint(oneLineRange
->EndRef());
4446 if (!endPoint
.IsStartOfContainer()) {
4449 if (startPoint
.GetContainer() != commonAncestor
) {
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
;
4459 if (!pointInParent
.IsEndOfContainer()) {
4464 if (endPoint
.GetContainer() != commonAncestor
) {
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
;
4474 if (!pointInParent
.IsStartOfContainer()) {
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
)) {
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");
4524 offset
= result
.NextInsertionPointRef().Offset();
4525 // MOZ_KnownLive because 'arrayOfContents' is guaranteed to
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());
4544 // XXX Different from the above block, we ignore error of moving nodes.
4545 // MOZ_KnownLive because 'arrayOfContents' is guaranteed to
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(
4564 "Last HTMLEditor::MoveNodeOrChildrenWithTransaction() failed");
4568 Result
<bool, nsresult
> HTMLEditor::CanMoveNodeOrChildren(
4569 const nsIContent
& aContent
, const nsINode
& aNewContainer
) const {
4570 if (HTMLEditUtils::CanNodeContain(aNewContainer
, aContent
)) {
4573 if (aContent
.IsElement()) {
4574 return CanMoveChildren(*aContent
.AsElement(), aNewContainer
);
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()),
4607 if (result
.Failed()) {
4608 NS_WARNING("HTMLEditor::MoveChildrenWithTransaction() failed");
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()));
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()) {
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");
4671 void HTMLEditor::MoveAllChildren(nsINode
& aContainer
,
4672 const EditorRawDOMPoint
& aPointToInsert
,
4673 ErrorResult
& aError
) {
4674 MOZ_ASSERT(!aError
.Failed());
4676 if (!aContainer
.HasChildren()) {
4679 nsIContent
* firstChild
= aContainer
.GetFirstChild();
4680 if (NS_WARN_IF(!firstChild
)) {
4681 aError
.Throw(NS_ERROR_FAILURE
);
4684 nsIContent
* lastChild
= aContainer
.GetLastChild();
4685 if (NS_WARN_IF(!lastChild
)) {
4686 aError
.Throw(NS_ERROR_FAILURE
);
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
);
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
) {
4716 if (NS_WARN_IF(children
.LastElement() != &aLastChild
)) {
4717 aError
.Throw(NS_ERROR_INVALID_ARG
);
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
4730 oldContainer
->RemoveChild(*child
, aError
);
4731 if (aError
.Failed()) {
4732 NS_WARNING("nsINode::RemoveChild() failed");
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
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
);
4749 newContainer
->InsertBefore(*child
, nextNode
, aError
);
4750 if (aError
.Failed()) {
4751 NS_WARNING("nsINode::InsertBefore() failed");
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
) {
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
);
4771 nsIContent
* firstChild
= aChild
.GetParentNode()->GetFirstChild();
4772 if (NS_WARN_IF(!firstChild
)) {
4773 aError
.Throw(NS_ERROR_FAILURE
);
4776 nsIContent
* lastChild
=
4777 &aChild
== firstChild
? firstChild
: aChild
.GetPreviousSibling();
4778 if (NS_WARN_IF(!lastChild
)) {
4779 aError
.Throw(NS_ERROR_FAILURE
);
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");
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
4817 DeleteContentButKeepTableStructure(aHTMLEditor
, MOZ_KnownLive(child
));
4818 if (NS_FAILED(rv
)) {
4819 NS_WARNING("HTMLEditor::DeleteContentButKeepTableStructure() failed");
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
) {
4836 bool seenBR
= false;
4837 if (!IsEmptyNodeImpl(*mailCiteElement
, true, true, false, &seenBR
)) {
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");
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");
4861 RefPtr
<Element
> brElement
=
4862 InsertBRElementWithTransaction(atEmptyMailCiteElement
);
4863 if (NS_WARN_IF(Destroyed())) {
4864 return NS_ERROR_EDITOR_DESTROYED
;
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(
4876 "HTMLEditor::::CollapseSelectionTo() failed, but ignored");
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
)) {
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
) {
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
) {
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
:
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
4945 TableBoundary::NoCrossAnyTableElement
);
4946 if (!startPoint
.IsSet()) {
4948 "HTMLEditUtils::GetPreviousEditablePoint() didn't return a valid "
4950 return NS_ERROR_FAILURE
;
4952 nsresult rv
= aRangesToDelete
.SetStartAndEnd(
4954 EditorRawDOMPoint::AtEndOf(mEmptyInclusiveAncestorBlockElement
));
4955 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
4956 "AutoRangeArray::SetStartAndEnd() failed");
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
4971 TableBoundary::NoCrossAnyTableElement
);
4972 if (!endPoint
.IsSet()) {
4974 "HTMLEditUtils::GetNextEditablePoint() didn't return a valid "
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");
4985 MOZ_ASSERT_UNREACHABLE("Handle the nsIEditor::EDirection value");
4988 // No direction, let's select the element to be deleted.
4990 aRangesToDelete
.SelectNode(*mEmptyInclusiveAncestorBlockElement
);
4991 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
), "AutoRangeArray::SelectNode() failed");
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
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
);
5029 NS_WARNING("HTMLEditor::InsertBRElementWithTransaction() failed");
5030 return Err(NS_ERROR_FAILURE
);
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
);
5057 NS_WARNING("HTMLEditor::GetGoodCaretPointFor() failed");
5058 return Err(NS_ERROR_FAILURE
);
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
);
5078 NS_WARNING("HTMLEditor::GetGoodCaretPointFor() failed");
5079 return Err(NS_ERROR_FAILURE
);
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();
5094 "AutoEmptyBlockAncestorDeleter doesn't support this action yet");
5095 return EditorDOMPoint();
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()) {
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()) {
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
);
5126 Result
<EditorDOMPoint
, nsresult
> result
=
5127 GetNewCaretPoisition(aHTMLEditor
, aDirectionAndAmount
);
5128 if (result
.isErr()) {
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())) {
5168 // Find current selection common block parent
5169 Element
* commonAncestorBlock
=
5170 HTMLEditUtils::GetInclusiveAncestorBlockElement(
5171 *aRange
.GetClosestCommonInclusiveAncestor()->AsContent());
5172 if (NS_WARN_IF(!commonAncestorBlock
)) {
5176 // Set up for loops and cache our root element
5177 Element
* editingHost
= aHTMLEditor
.GetActiveEditingHost();
5178 if (NS_WARN_IF(!editingHost
)) {
5182 // Find previous visible things before start of selection
5183 if (atStart
.GetContainer() != commonAncestorBlock
&&
5184 atStart
.GetContainer() != editingHost
) {
5186 WSScanResult backwardScanFromStartResult
=
5187 WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary(aHTMLEditor
,
5189 if (!backwardScanFromStartResult
.ReachedCurrentBlockBoundary()) {
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
) {
5202 atStart
= backwardScanFromStartResult
.PointAtContent();
5204 if (aFrameSelection
&&
5205 !aFrameSelection
->IsValidSelectionPoint(atStart
.GetContainer())) {
5206 NS_WARNING("Computed start container was out of selection limiter");
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
5215 // Find next visible things after end of selection
5216 if (atEnd
.GetContainer() != commonAncestorBlock
&&
5217 atEnd
.GetContainer() != editingHost
) {
5218 EditorDOMPoint atFirstInvisibleBRElement
;
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())) {
5236 if (!atFirstInvisibleBRElement
.IsSet()) {
5237 atFirstInvisibleBRElement
= atEnd
;
5239 atEnd
.SetAfter(wsScannerAtEnd
.GetEndReasonContent());
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
) {
5254 atEnd
= forwardScanFromEndResult
.PointAfterContent();
5261 if (aFrameSelection
&&
5262 !aFrameSelection
->IsValidSelectionPoint(atEnd
.GetContainer())) {
5263 NS_WARNING("Computed end container was out of selection limiter");
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");
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())) {
5287 "Computed end container (`<br>` element) was out of selection "
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");
5304 return aRange
.IsPositioned() && aRange
.StartRef().IsSet() &&
5305 aRange
.EndRef().IsSet();
5308 } // namespace mozilla