summaryrefslogtreecommitdiffstats
path: root/editor/libeditor/HTMLEditorDeleteHandler.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-06-12 05:35:29 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-06-12 05:35:29 +0000
commit59203c63bb777a3bacec32fb8830fba33540e809 (patch)
tree58298e711c0ff0575818c30485b44a2f21bf28a0 /editor/libeditor/HTMLEditorDeleteHandler.cpp
parentAdding upstream version 126.0.1. (diff)
downloadfirefox-59203c63bb777a3bacec32fb8830fba33540e809.tar.xz
firefox-59203c63bb777a3bacec32fb8830fba33540e809.zip
Adding upstream version 127.0.upstream/127.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'editor/libeditor/HTMLEditorDeleteHandler.cpp')
-rw-r--r--editor/libeditor/HTMLEditorDeleteHandler.cpp1104
1 files changed, 942 insertions, 162 deletions
diff --git a/editor/libeditor/HTMLEditorDeleteHandler.cpp b/editor/libeditor/HTMLEditorDeleteHandler.cpp
index 39bb95151e..8c58979001 100644
--- a/editor/libeditor/HTMLEditorDeleteHandler.cpp
+++ b/editor/libeditor/HTMLEditorDeleteHandler.cpp
@@ -353,6 +353,28 @@ class MOZ_STACK_CLASS HTMLEditor::AutoDeleteRangesHandler final {
const EditorDOMRangeType& aRangeToDelete) const;
/**
+ * Extend the start boundary of aRangeToDelete to contain ancestor inline
+ * elements which will be empty once the content in aRangeToDelete is removed
+ * from the tree.
+ *
+ * NOTE: This is designed for deleting inline elements which become empty if
+ * aRangeToDelete which crosses a block boundary of right block child.
+ * Therefore, you may need to improve this method if you want to use this in
+ * the other cases.
+ *
+ * @param aRangeToDelete [in/out] The range to delete. This start
+ * boundary may be modified.
+ * @param aEditingHost The editing host.
+ * @return true if aRangeToDelete is modified.
+ * false if aRangeToDelete is not modified.
+ * error if aRangeToDelete gets unexpected
+ * situation.
+ */
+ static Result<bool, nsresult>
+ ExtendRangeToContainAncestorInlineElementsAtStart(
+ nsRange& aRangeToDelete, const Element& aEditingHost);
+
+ /**
* A helper method for ExtendOrShrinkRangeToDelete(). This returns shrunken
* range if aRangeToDelete selects all over list elements which have some list
* item elements to avoid to delete all list items from the list element.
@@ -529,13 +551,15 @@ class MOZ_STACK_CLASS HTMLEditor::AutoDeleteRangesHandler final {
* @param aCurrentBlockElement The current block element.
* @param aCaretPoint The caret point (i.e., selection start
* or end).
+ * @param aEditingHost The editing host.
* @return true if can continue to handle the
* deletion.
*/
bool PrepareToDeleteAtCurrentBlockBoundary(
const HTMLEditor& aHTMLEditor,
nsIEditor::EDirection aDirectionAndAmount,
- Element& aCurrentBlockElement, const EditorDOMPoint& aCaretPoint);
+ Element& aCurrentBlockElement, const EditorDOMPoint& aCaretPoint,
+ const Element& aEditingHost);
/**
* PrepareToDeleteAtOtherBlockBoundary() considers left content and right
@@ -567,11 +591,13 @@ class MOZ_STACK_CLASS HTMLEditor::AutoDeleteRangesHandler final {
* @param aHTMLEditor The HTML editor.
* @param aRangeToDelete The range to delete. Must not be
* collapsed.
+ * @param aEditingHost The editing host.
* @return true if can continue to handle the
* deletion.
*/
bool PrepareToDeleteNonCollapsedRange(const HTMLEditor& aHTMLEditor,
- const nsRange& aRangeToDelete);
+ const nsRange& aRangeToDelete,
+ const Element& aEditingHost);
/**
* Run() executes the joining.
@@ -609,17 +635,20 @@ class MOZ_STACK_CLASS HTMLEditor::AutoDeleteRangesHandler final {
"HandleDeleteAtOtherBlockBoundary() failed");
return result;
}
- case Mode::DeleteBRElement: {
- Result<EditActionResult, nsresult> result =
- DeleteBRElement(aHTMLEditor, aDirectionAndAmount, aEditingHost);
+ case Mode::DeleteBRElement:
+ case Mode::DeletePrecedingBRElementOfBlock:
+ case Mode::DeletePrecedingPreformattedLineBreak: {
+ Result<EditActionResult, nsresult> result = HandleDeleteLineBreak(
+ aHTMLEditor, aDirectionAndAmount, aCaretPoint, aEditingHost);
NS_WARNING_ASSERTION(
result.isOk(),
- "AutoBlockElementsJoiner::DeleteBRElement() failed");
+ "AutoBlockElementsJoiner::HandleDeleteLineBreak() failed");
return result;
}
case Mode::JoinBlocksInSameParent:
case Mode::DeleteContentInRange:
case Mode::DeleteNonCollapsedRange:
+ case Mode::DeletePrecedingLinesAndContentInRange:
MOZ_ASSERT_UNREACHABLE(
"This mode should be handled in the other Run()");
return Err(NS_ERROR_UNEXPECTED);
@@ -654,16 +683,21 @@ class MOZ_STACK_CLASS HTMLEditor::AutoDeleteRangesHandler final {
"ComputeRangeToDeleteAtOtherBlockBoundary() failed");
return rv;
}
- case Mode::DeleteBRElement: {
- nsresult rv = ComputeRangeToDeleteBRElement(aRangeToDelete);
+ case Mode::DeleteBRElement:
+ case Mode::DeletePrecedingBRElementOfBlock:
+ case Mode::DeletePrecedingPreformattedLineBreak: {
+ nsresult rv = ComputeRangeToDeleteLineBreak(
+ aHTMLEditor, aRangeToDelete, aEditingHost,
+ ComputeRangeFor::GetTargetRanges);
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
"AutoBlockElementsJoiner::"
- "ComputeRangeToDeleteBRElement() failed");
+ "ComputeRangeToDeleteLineBreak() failed");
return rv;
}
case Mode::JoinBlocksInSameParent:
case Mode::DeleteContentInRange:
case Mode::DeleteNonCollapsedRange:
+ case Mode::DeletePrecedingLinesAndContentInRange:
MOZ_ASSERT_UNREACHABLE(
"This mode should be handled in the other "
"ComputeRangesToDelete()");
@@ -696,6 +730,8 @@ class MOZ_STACK_CLASS HTMLEditor::AutoDeleteRangesHandler final {
case Mode::JoinCurrentBlock:
case Mode::JoinOtherBlock:
case Mode::DeleteBRElement:
+ case Mode::DeletePrecedingBRElementOfBlock:
+ case Mode::DeletePrecedingPreformattedLineBreak:
MOZ_ASSERT_UNREACHABLE(
"This mode should be handled in the other Run()");
return Err(NS_ERROR_UNEXPECTED);
@@ -717,7 +753,8 @@ class MOZ_STACK_CLASS HTMLEditor::AutoDeleteRangesHandler final {
"AutoBlockElementsJoiner::DeleteContentInRange() failed");
return result;
}
- case Mode::DeleteNonCollapsedRange: {
+ case Mode::DeleteNonCollapsedRange:
+ case Mode::DeletePrecedingLinesAndContentInRange: {
Result<EditActionResult, nsresult> result =
HandleDeleteNonCollapsedRange(
aHTMLEditor, aDirectionAndAmount, aStripWrappers,
@@ -744,6 +781,8 @@ class MOZ_STACK_CLASS HTMLEditor::AutoDeleteRangesHandler final {
case Mode::JoinCurrentBlock:
case Mode::JoinOtherBlock:
case Mode::DeleteBRElement:
+ case Mode::DeletePrecedingBRElementOfBlock:
+ case Mode::DeletePrecedingPreformattedLineBreak:
MOZ_ASSERT_UNREACHABLE(
"This mode should be handled in the other "
"ComputeRangesToDelete()");
@@ -765,7 +804,8 @@ class MOZ_STACK_CLASS HTMLEditor::AutoDeleteRangesHandler final {
"ComputeRangesToDeleteContentInRanges() failed");
return rv;
}
- case Mode::DeleteNonCollapsedRange: {
+ case Mode::DeleteNonCollapsedRange:
+ case Mode::DeletePrecedingLinesAndContentInRange: {
nsresult rv = ComputeRangeToDeleteNonCollapsedRange(
aHTMLEditor, aDirectionAndAmount, aRangeToDelete,
aSelectionWasCollapsed, aEditingHost);
@@ -825,10 +865,14 @@ class MOZ_STACK_CLASS HTMLEditor::AutoDeleteRangesHandler final {
nsIEditor::EDirection aDirectionAndAmount, nsRange& aRangeToDelete,
const Element& aEditingHost) const;
[[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditActionResult, nsresult>
- DeleteBRElement(HTMLEditor& aHTMLEditor,
- nsIEditor::EDirection aDirectionAndAmount,
- const Element& aEditingHost);
- nsresult ComputeRangeToDeleteBRElement(nsRange& aRangeToDelete) const;
+ HandleDeleteLineBreak(HTMLEditor& aHTMLEditor,
+ nsIEditor::EDirection aDirectionAndAmount,
+ const EditorDOMPoint& aCaretPoint,
+ const Element& aEditingHost);
+ enum class ComputeRangeFor : bool { GetTargetRanges, ToDeleteTheRange };
+ nsresult ComputeRangeToDeleteLineBreak(
+ const HTMLEditor& aHTMLEditor, nsRange& aRangeToDelete,
+ const Element& aEditingHost, ComputeRangeFor aComputeRangeFor) const;
[[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditActionResult, nsresult>
DeleteContentInRange(HTMLEditor& aHTMLEditor,
nsIEditor::EDirection aDirectionAndAmount,
@@ -913,6 +957,27 @@ class MOZ_STACK_CLASS HTMLEditor::AutoDeleteRangesHandler final {
[[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult
DeleteTextAtStartAndEndOfRange(HTMLEditor& aHTMLEditor, nsRange& aRange);
+ /**
+ * Return a block element which is an inclusive ancestor of the container of
+ * aPoint if aPoint is start of ancestor blocks. For example, if `<div
+ * id=div1>abc<div id=div2><div id=div3>[]def</div></div></div>`, return
+ * #div2.
+ */
+ template <typename EditorDOMPointType>
+ static Result<Element*, nsresult>
+ GetMostDistantBlockAncestorIfPointIsStartAtBlock(
+ const EditorDOMPointType& aPoint, const Element& aEditingHost,
+ const Element* aAncestorLimiter = nullptr);
+
+ /**
+ * Extend aRangeToDelete to contain new empty inline ancestors and contain
+ * an invisible <br> element before right child block which causes an empty
+ * line but the range starts after it.
+ */
+ void ExtendRangeToDeleteNonCollapsedRange(
+ const HTMLEditor& aHTMLEditor, nsRange& aRangeToDelete,
+ const Element& aEditingHost, ComputeRangeFor aComputeRangeFor) const;
+
class MOZ_STACK_CLASS AutoInclusiveAncestorBlockElementsJoiner final {
public:
AutoInclusiveAncestorBlockElementsJoiner() = delete;
@@ -1030,8 +1095,17 @@ class MOZ_STACK_CLASS HTMLEditor::AutoDeleteRangesHandler final {
JoinOtherBlock,
JoinBlocksInSameParent,
DeleteBRElement,
+ // The instance will handle only the <br> element immediately before a
+ // block.
+ DeletePrecedingBRElementOfBlock,
+ // The instance will handle only the preceding preformatted line break
+ // before a block.
+ DeletePrecedingPreformattedLineBreak,
DeleteContentInRange,
DeleteNonCollapsedRange,
+ // The instance will handle preceding lines of the right block and content
+ // in the range in the right block.
+ DeletePrecedingLinesAndContentInRange,
};
AutoDeleteRangesHandler* mDeleteRangesHandler;
const AutoDeleteRangesHandler& mDeleteRangesHandlerConst;
@@ -1043,6 +1117,7 @@ class MOZ_STACK_CLASS HTMLEditor::AutoDeleteRangesHandler final {
// removed at deletion.
AutoTArray<OwningNonNull<nsIContent>, 8> mSkippedInvisibleContents;
RefPtr<dom::HTMLBRElement> mBRElement;
+ EditorDOMPointInText mPreformattedLineBreak;
Mode mMode = Mode::NotInitialized;
}; // HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner
@@ -1371,10 +1446,10 @@ nsresult HTMLEditor::AutoDeleteRangesHandler::ComputeRangesToDelete(
WSRunScanner wsRunScannerAtCaret(
editingHost, caretPoint,
BlockInlineCheck::UseComputedDisplayOutsideStyle);
- WSScanResult scanFromCaretPointResult =
+ const WSScanResult scanFromCaretPointResult =
aDirectionAndAmount == nsIEditor::eNext
- ? wsRunScannerAtCaret.ScanNextVisibleNodeOrBlockBoundaryFrom(
- caretPoint)
+ ? wsRunScannerAtCaret
+ .ScanInclusiveNextVisibleNodeOrBlockBoundaryFrom(caretPoint)
: wsRunScannerAtCaret.ScanPreviousVisibleNodeOrBlockBoundaryFrom(
caretPoint);
if (scanFromCaretPointResult.Failed()) {
@@ -1383,26 +1458,23 @@ nsresult HTMLEditor::AutoDeleteRangesHandler::ComputeRangesToDelete(
"failed");
return NS_ERROR_FAILURE;
}
- if (!scanFromCaretPointResult.GetContent()) {
- return NS_SUCCESS_DOM_NO_OPERATION;
- }
+ MOZ_ASSERT(scanFromCaretPointResult.GetContent());
if (scanFromCaretPointResult.ReachedBRElement()) {
if (scanFromCaretPointResult.BRElementPtr() ==
wsRunScannerAtCaret.GetEditingHost()) {
return NS_OK;
}
- if (!EditorUtils::IsEditableContent(
- *scanFromCaretPointResult.BRElementPtr(), EditorType::HTML)) {
+ if (!scanFromCaretPointResult.IsContentEditable()) {
return NS_SUCCESS_DOM_NO_OPERATION;
}
- if (HTMLEditUtils::IsInvisibleBRElement(
- *scanFromCaretPointResult.BRElementPtr())) {
+ if (scanFromCaretPointResult.ReachedInvisibleBRElement()) {
EditorDOMPoint newCaretPosition =
aDirectionAndAmount == nsIEditor::eNext
- ? EditorDOMPoint::After(
- *scanFromCaretPointResult.BRElementPtr())
- : EditorDOMPoint(scanFromCaretPointResult.BRElementPtr());
+ ? scanFromCaretPointResult
+ .PointAfterReachedContent<EditorDOMPoint>()
+ : scanFromCaretPointResult
+ .PointAtReachedContent<EditorDOMPoint>();
if (NS_WARN_IF(!newCaretPosition.IsSet())) {
return NS_ERROR_FAILURE;
}
@@ -1450,7 +1522,8 @@ nsresult HTMLEditor::AutoDeleteRangesHandler::ComputeRangesToDelete(
// Otherwise, extend the range to contain the invisible `<br>`
// element.
- if (EditorRawDOMPoint(scanFromCaretPointResult.BRElementPtr())
+ if (scanFromCaretPointResult
+ .PointAtReachedContent<EditorRawDOMPoint>()
.IsBefore(
aRangesToDelete
.GetFirstRangeStartPoint<EditorRawDOMPoint>())) {
@@ -1463,12 +1536,13 @@ nsresult HTMLEditor::AutoDeleteRangesHandler::ComputeRangesToDelete(
return rv;
}
if (aRangesToDelete.GetFirstRangeEndPoint<EditorRawDOMPoint>()
- .IsBefore(EditorRawDOMPoint::After(
- *scanFromCaretPointResult.BRElementPtr()))) {
+ .IsBefore(
+ scanFromCaretPointResult
+ .PointAfterReachedContent<EditorRawDOMPoint>())) {
nsresult rv = aRangesToDelete.FirstRangeRef()->SetStartAndEnd(
aRangesToDelete.FirstRangeRef()->StartRef(),
- EditorRawDOMPoint::After(
- *scanFromCaretPointResult.BRElementPtr())
+ scanFromCaretPointResult
+ .PointAfterReachedContent<EditorRawDOMPoint>()
.ToRawRangeBoundary());
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
"nsRange::SetStartAndEnd() failed");
@@ -1670,10 +1744,11 @@ Result<EditActionResult, nsresult> HTMLEditor::AutoDeleteRangesHandler::Run(
WSRunScanner wsRunScannerAtCaret(
&aEditingHost, caretPoint.ref(),
BlockInlineCheck::UseComputedDisplayOutsideStyle);
- WSScanResult scanFromCaretPointResult =
+ const WSScanResult scanFromCaretPointResult =
aDirectionAndAmount == nsIEditor::eNext
- ? wsRunScannerAtCaret.ScanNextVisibleNodeOrBlockBoundaryFrom(
- caretPoint.ref())
+ ? wsRunScannerAtCaret
+ .ScanInclusiveNextVisibleNodeOrBlockBoundaryFrom(
+ caretPoint.ref())
: wsRunScannerAtCaret.ScanPreviousVisibleNodeOrBlockBoundaryFrom(
caretPoint.ref());
if (MOZ_UNLIKELY(scanFromCaretPointResult.Failed())) {
@@ -1682,20 +1757,17 @@ Result<EditActionResult, nsresult> HTMLEditor::AutoDeleteRangesHandler::Run(
"failed");
return Err(NS_ERROR_FAILURE);
}
- if (!scanFromCaretPointResult.GetContent()) {
- return EditActionResult::CanceledResult();
- }
+ MOZ_ASSERT(scanFromCaretPointResult.GetContent());
+
// Short circuit for invisible breaks. delete them and recurse.
if (scanFromCaretPointResult.ReachedBRElement()) {
if (scanFromCaretPointResult.BRElementPtr() == &aEditingHost) {
return EditActionResult::HandledResult();
}
- if (!EditorUtils::IsEditableContent(
- *scanFromCaretPointResult.BRElementPtr(), EditorType::HTML)) {
+ if (!scanFromCaretPointResult.IsContentEditable()) {
return EditActionResult::CanceledResult();
}
- if (HTMLEditUtils::IsInvisibleBRElement(
- *scanFromCaretPointResult.BRElementPtr())) {
+ if (scanFromCaretPointResult.ReachedInvisibleBRElement()) {
// TODO: We should extend the range to delete again before/after
// the caret point and use `HandleDeleteNonCollapsedRanges()`
// instead after we would create delete range computation
@@ -1728,10 +1800,10 @@ Result<EditActionResult, nsresult> HTMLEditor::AutoDeleteRangesHandler::Run(
WSRunScanner wsRunScannerAtCaret(
&aEditingHost, caretPoint.ref(),
BlockInlineCheck::UseComputedDisplayOutsideStyle);
- WSScanResult scanFromCaretPointResult =
+ const WSScanResult scanFromCaretPointResult =
aDirectionAndAmount == nsIEditor::eNext
? wsRunScannerAtCaret
- .ScanNextVisibleNodeOrBlockBoundaryFrom(
+ .ScanInclusiveNextVisibleNodeOrBlockBoundaryFrom(
caretPoint.ref())
: wsRunScannerAtCaret
.ScanPreviousVisibleNodeOrBlockBoundaryFrom(
@@ -1742,7 +1814,7 @@ Result<EditActionResult, nsresult> HTMLEditor::AutoDeleteRangesHandler::Run(
"VisibleNodeOrBlockBoundaryFrom() failed");
return Err(NS_ERROR_FAILURE);
}
- if (MOZ_UNLIKELY(
+ if (NS_WARN_IF(
scanFromCaretPointResult.ReachedInvisibleBRElement())) {
return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
}
@@ -1786,8 +1858,11 @@ HTMLEditor::AutoDeleteRangesHandler::ComputeRangesToDeleteAroundCollapsedRanges(
if (aScanFromCaretPointResult.InCollapsibleWhiteSpaces() ||
aScanFromCaretPointResult.InNonCollapsibleCharacters() ||
aScanFromCaretPointResult.ReachedPreformattedLineBreak()) {
+ // This means that if aDirectionAndAmount == nsIEditor::eNext, collapse
+ // selection at the found character. Otherwise, collapse selection after
+ // the found character.
nsresult rv = aRangesToDelete.Collapse(
- aScanFromCaretPointResult.Point<EditorRawDOMPoint>());
+ aScanFromCaretPointResult.Point_Deprecated<EditorRawDOMPoint>());
if (MOZ_UNLIKELY(NS_FAILED(rv))) {
NS_WARNING("AutoRangeArray::Collapse() failed");
return NS_ERROR_FAILURE;
@@ -1826,7 +1901,7 @@ HTMLEditor::AutoDeleteRangesHandler::ComputeRangesToDeleteAroundCollapsedRanges(
}
if (aScanFromCaretPointResult.ReachedOtherBlockElement()) {
- if (NS_WARN_IF(!aScanFromCaretPointResult.GetContent()->IsElement())) {
+ if (NS_WARN_IF(!aScanFromCaretPointResult.ContentIsElement())) {
return NS_ERROR_FAILURE;
}
MOZ_ASSERT(!aRangesToDelete.Ranges().IsEmpty());
@@ -1854,10 +1929,9 @@ HTMLEditor::AutoDeleteRangesHandler::ComputeRangesToDeleteAroundCollapsedRanges(
return handled ? NS_OK : NS_SUCCESS_DOM_NO_OPERATION;
}
- if (aScanFromCaretPointResult.ReachedCurrentBlockBoundary()) {
- if (NS_WARN_IF(!aScanFromCaretPointResult.GetContent()->IsElement())) {
- return NS_ERROR_FAILURE;
- }
+ if (aScanFromCaretPointResult.ReachedCurrentBlockBoundary() ||
+ aScanFromCaretPointResult.ReachedInlineEditingHostBoundary()) {
+ MOZ_ASSERT(aScanFromCaretPointResult.ContentIsElement());
MOZ_ASSERT(!aRangesToDelete.Ranges().IsEmpty());
bool handled = false;
for (const OwningNonNull<nsRange>& range : aRangesToDelete.Ranges()) {
@@ -1865,7 +1939,7 @@ HTMLEditor::AutoDeleteRangesHandler::ComputeRangesToDeleteAroundCollapsedRanges(
if (!joiner.PrepareToDeleteAtCurrentBlockBoundary(
aHTMLEditor, aDirectionAndAmount,
*aScanFromCaretPointResult.ElementPtr(),
- aWSRunScannerAtCaret.ScanStartRef())) {
+ aWSRunScannerAtCaret.ScanStartRef(), aEditingHost)) {
continue;
}
handled = true;
@@ -1904,8 +1978,11 @@ HTMLEditor::AutoDeleteRangesHandler::HandleDeleteAroundCollapsedRanges(
if (aScanFromCaretPointResult.InCollapsibleWhiteSpaces() ||
aScanFromCaretPointResult.InNonCollapsibleCharacters() ||
aScanFromCaretPointResult.ReachedPreformattedLineBreak()) {
+ // This means that if aDirectionAndAmount == nsIEditor::eNext, collapse
+ // selection at the found character. Otherwise, collapse selection after
+ // the found character.
nsresult rv = aRangesToDelete.Collapse(
- aScanFromCaretPointResult.Point<EditorRawDOMPoint>());
+ aScanFromCaretPointResult.Point_Deprecated<EditorRawDOMPoint>());
if (NS_FAILED(rv)) {
NS_WARNING("AutoRangeArray::Collapse() failed");
return Err(NS_ERROR_FAILURE);
@@ -1959,13 +2036,16 @@ HTMLEditor::AutoDeleteRangesHandler::HandleDeleteAroundCollapsedRanges(
}
if (aScanFromCaretPointResult.InNonCollapsibleCharacters()) {
- if (NS_WARN_IF(!aScanFromCaretPointResult.GetContent()->IsText())) {
+ if (NS_WARN_IF(!aScanFromCaretPointResult.ContentIsText())) {
return Err(NS_ERROR_FAILURE);
}
Result<CaretPoint, nsresult> caretPointOrError =
HandleDeleteCollapsedSelectionAtVisibleChar(
aHTMLEditor, aDirectionAndAmount, aRangesToDelete,
- aScanFromCaretPointResult.Point<EditorDOMPoint>(), aEditingHost);
+ // This means that if aDirectionAndAmount == nsIEditor::eNext,
+ // at the found character. Otherwise, after the found character.
+ aScanFromCaretPointResult.Point_Deprecated<EditorDOMPoint>(),
+ aEditingHost);
if (MOZ_UNLIKELY(caretPointOrError.isErr())) {
NS_WARNING(
"AutoDeleteRangesHandler::"
@@ -2019,7 +2099,7 @@ HTMLEditor::AutoDeleteRangesHandler::HandleDeleteAroundCollapsedRanges(
}
if (aScanFromCaretPointResult.ReachedOtherBlockElement()) {
- if (NS_WARN_IF(!aScanFromCaretPointResult.GetContent()->IsElement())) {
+ if (NS_WARN_IF(!aScanFromCaretPointResult.ContentIsElement())) {
return Err(NS_ERROR_FAILURE);
}
MOZ_ASSERT(!aRangesToDelete.Ranges().IsEmpty());
@@ -2049,10 +2129,9 @@ HTMLEditor::AutoDeleteRangesHandler::HandleDeleteAroundCollapsedRanges(
: std::move(ret);
}
- if (aScanFromCaretPointResult.ReachedCurrentBlockBoundary()) {
- if (NS_WARN_IF(!aScanFromCaretPointResult.GetContent()->IsElement())) {
- return Err(NS_ERROR_FAILURE);
- }
+ if (aScanFromCaretPointResult.ReachedCurrentBlockBoundary() ||
+ aScanFromCaretPointResult.ReachedInlineEditingHostBoundary()) {
+ MOZ_ASSERT(aScanFromCaretPointResult.ContentIsElement());
MOZ_ASSERT(!aRangesToDelete.Ranges().IsEmpty());
bool allRangesNotHandled = true;
auto ret = EditActionResult::IgnoredResult();
@@ -2061,7 +2140,7 @@ HTMLEditor::AutoDeleteRangesHandler::HandleDeleteAroundCollapsedRanges(
if (!joiner.PrepareToDeleteAtCurrentBlockBoundary(
aHTMLEditor, aDirectionAndAmount,
*aScanFromCaretPointResult.ElementPtr(),
- aWSRunScannerAtCaret.ScanStartRef())) {
+ aWSRunScannerAtCaret.ScanStartRef(), aEditingHost)) {
continue;
}
allRangesNotHandled = false;
@@ -2484,6 +2563,109 @@ HTMLEditor::AutoDeleteRangesHandler::HandleDeleteAtomicContent(
return CaretPoint(std::move(pointToPutCaret));
}
+// static
+Result<bool, nsresult> HTMLEditor::AutoDeleteRangesHandler::
+ ExtendRangeToContainAncestorInlineElementsAtStart(
+ nsRange& aRangeToDelete, const Element& aEditingHost) {
+ MOZ_ASSERT(aRangeToDelete.IsPositioned());
+ MOZ_ASSERT(aRangeToDelete.GetCommonAncestorContainer(IgnoreErrors()));
+ MOZ_ASSERT(aRangeToDelete.GetCommonAncestorContainer(IgnoreErrors())
+ ->IsInclusiveDescendantOf(&aEditingHost));
+
+ EditorRawDOMPoint startPoint(aRangeToDelete.StartRef());
+ if (startPoint.IsInTextNode()) {
+ if (!startPoint.IsStartOfContainer()) {
+ // FIXME: If before the point has only collapsible white-spaces and the
+ // text node follows a block boundary, we should treat the range start
+ // from start of the text node.
+ return true;
+ }
+ startPoint.Set(startPoint.ContainerAs<Text>());
+ if (NS_WARN_IF(!startPoint.IsSet())) {
+ return Err(NS_ERROR_FAILURE);
+ }
+ if (startPoint.GetContainer() == &aEditingHost) {
+ return false;
+ }
+ } else if (startPoint.IsInDataNode()) {
+ startPoint.Set(startPoint.ContainerAs<nsIContent>());
+ if (NS_WARN_IF(!startPoint.IsSet())) {
+ return Err(NS_ERROR_FAILURE);
+ }
+ if (startPoint.GetContainer() == &aEditingHost) {
+ return false;
+ }
+ } else if (startPoint.GetContainer() == &aEditingHost) {
+ return false;
+ }
+
+ // FYI: This method is designed for deleting inline elements which become
+ // empty if aRangeToDelete which crosses a block boundary of right block
+ // child. Therefore, you may need to improve this method if you want to use
+ // this in the other cases.
+
+ nsINode* const commonAncestor =
+ nsContentUtils::GetClosestCommonInclusiveAncestor(
+ startPoint.GetContainer(), aRangeToDelete.GetEndContainer());
+ if (NS_WARN_IF(!commonAncestor)) {
+ return Err(NS_ERROR_FAILURE);
+ }
+ MOZ_ASSERT(commonAncestor->IsInclusiveDescendantOf(&aEditingHost));
+
+ EditorRawDOMPoint newStartPoint(startPoint);
+ while (newStartPoint.GetContainer() != &aEditingHost &&
+ newStartPoint.GetContainer() != commonAncestor) {
+ if (NS_WARN_IF(!newStartPoint.IsInContentNode())) {
+ return Err(NS_ERROR_FAILURE);
+ }
+ if (!HTMLEditUtils::IsInlineContent(
+ *newStartPoint.ContainerAs<nsIContent>(),
+ BlockInlineCheck::UseComputedDisplayOutsideStyle)) {
+ break;
+ }
+ // The container is inline, check whether the point is first visible point
+ // or not to consider whether climbing up the tree.
+ bool foundVisiblePrevSibling = false;
+ for (nsIContent* content = newStartPoint.GetPreviousSiblingOfChild();
+ content; content = content->GetPreviousSibling()) {
+ if (Text* text = Text::FromNode(content)) {
+ if (HTMLEditUtils::IsVisibleTextNode(*text)) {
+ foundVisiblePrevSibling = true;
+ break;
+ }
+ // The text node is invisible.
+ } else if (content->IsComment()) {
+ // Ignore the comment node.
+ } else if (!HTMLEditUtils::IsInlineContent(
+ *content,
+ BlockInlineCheck::UseComputedDisplayOutsideStyle) ||
+ !HTMLEditUtils::IsEmptyNode(
+ *content,
+ {EmptyCheckOption::TreatSingleBRElementAsVisible})) {
+ foundVisiblePrevSibling = true;
+ break;
+ }
+ }
+ if (foundVisiblePrevSibling) {
+ break;
+ }
+ // the point can be treated as start of the parent inline now.
+ newStartPoint.Set(newStartPoint.ContainerAs<nsIContent>());
+ if (NS_WARN_IF(!newStartPoint.IsSet())) {
+ return Err(NS_ERROR_FAILURE);
+ }
+ }
+ if (newStartPoint == startPoint) {
+ return false; // Don't need to modify the range
+ }
+ IgnoredErrorResult error;
+ aRangeToDelete.SetStart(newStartPoint.ToRawRangeBoundary(), error);
+ if (MOZ_UNLIKELY(error.Failed())) {
+ return Err(NS_ERROR_FAILURE);
+ }
+ return true;
+}
+
bool HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
PrepareToDeleteAtOtherBlockBoundary(
const HTMLEditor& aHTMLEditor,
@@ -2519,12 +2701,12 @@ bool HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
// Next to a block. See if we are between the block and a `<br>`.
// If so, we really want to delete the `<br>`. Else join content at
// selection to the block.
- WSScanResult scanFromCaretResult =
+ const WSScanResult scanFromCaretResult =
aDirectionAndAmount == nsIEditor::eNext
? aWSRunScannerAtCaret.ScanPreviousVisibleNodeOrBlockBoundaryFrom(
aCaretPoint)
- : aWSRunScannerAtCaret.ScanNextVisibleNodeOrBlockBoundaryFrom(
- aCaretPoint);
+ : aWSRunScannerAtCaret
+ .ScanInclusiveNextVisibleNodeOrBlockBoundaryFrom(aCaretPoint);
// If we found a `<br>` element, we need to delete it instead of joining the
// contents.
if (scanFromCaretResult.ReachedBRElement()) {
@@ -2537,58 +2719,159 @@ bool HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
}
nsresult HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
- ComputeRangeToDeleteBRElement(nsRange& aRangeToDelete) const {
- MOZ_ASSERT(mBRElement);
- // XXX Why don't we scan invisible leading white-spaces which follows the
- // `<br>` element?
+ ComputeRangeToDeleteLineBreak(const HTMLEditor& aHTMLEditor,
+ nsRange& aRangeToDelete,
+ const Element& aEditingHost,
+ ComputeRangeFor aComputeRangeFor) const {
+ // FIXME: Scan invisible leading white-spaces after the <br>.
+ MOZ_ASSERT_IF(mMode == Mode::DeleteBRElement, mBRElement);
+ MOZ_ASSERT_IF(mMode == Mode::DeletePrecedingBRElementOfBlock, mBRElement);
+ MOZ_ASSERT_IF(mMode == Mode::DeletePrecedingPreformattedLineBreak,
+ mPreformattedLineBreak.IsSetAndValid());
+ MOZ_ASSERT_IF(mMode == Mode::DeletePrecedingPreformattedLineBreak,
+ mPreformattedLineBreak.IsCharPreformattedNewLine());
+ MOZ_ASSERT_IF(aComputeRangeFor == ComputeRangeFor::GetTargetRanges,
+ aRangeToDelete.IsPositioned());
+
+ // If we're computing for beforeinput.getTargetRanges() and the inputType
+ // is not a simple deletion like replacing selected content with new
+ // content, the range should end at the original end boundary of the given
+ // range.
+ const bool preserveEndBoundary =
+ (mMode == Mode::DeletePrecedingBRElementOfBlock ||
+ mMode == Mode::DeletePrecedingPreformattedLineBreak) &&
+ aComputeRangeFor == ComputeRangeFor::GetTargetRanges &&
+ !MayEditActionDeleteAroundCollapsedSelection(aHTMLEditor.GetEditAction());
+
+ if (mMode != Mode::DeletePrecedingPreformattedLineBreak) {
+ Element* const mostDistantInlineAncestor =
+ HTMLEditUtils::GetMostDistantAncestorEditableEmptyInlineElement(
+ *mBRElement, BlockInlineCheck::UseComputedDisplayOutsideStyle,
+ &aEditingHost);
+ if (preserveEndBoundary) {
+ // FIXME: If the range ends at end of an inline element, we may need to
+ // extend the range.
+ IgnoredErrorResult error;
+ aRangeToDelete.SetStart(EditorRawDOMPoint(mostDistantInlineAncestor
+ ? mostDistantInlineAncestor
+ : mBRElement)
+ .ToRawRangeBoundary(),
+ error);
+ NS_WARNING_ASSERTION(!error.Failed(), "nsRange::SetStart() failed");
+ MOZ_ASSERT_IF(!error.Failed(), !aRangeToDelete.Collapsed());
+ return error.StealNSResult();
+ }
+ IgnoredErrorResult error;
+ aRangeToDelete.SelectNode(
+ mostDistantInlineAncestor ? *mostDistantInlineAncestor : *mBRElement,
+ error);
+ NS_WARNING_ASSERTION(!error.Failed(), "nsRange::SelectNode() failed");
+ return error.StealNSResult();
+ }
+
+ Element* const mostDistantInlineAncestor =
+ mPreformattedLineBreak.ContainerAs<Text>()->TextDataLength() == 1
+ ? HTMLEditUtils::GetMostDistantAncestorEditableEmptyInlineElement(
+ *mPreformattedLineBreak.ContainerAs<Text>(),
+ BlockInlineCheck::UseComputedDisplayOutsideStyle, &aEditingHost)
+ : nullptr;
+
+ if (!mostDistantInlineAncestor) {
+ if (preserveEndBoundary) {
+ // FIXME: If the range ends at end of an inline element, we may need to
+ // extend the range.
+ IgnoredErrorResult error;
+ aRangeToDelete.SetStart(mPreformattedLineBreak.ToRawRangeBoundary(),
+ error);
+ MOZ_ASSERT_IF(!error.Failed(), !aRangeToDelete.Collapsed());
+ NS_WARNING_ASSERTION(!error.Failed(), "nsRange::SetStart() failed");
+ return error.StealNSResult();
+ }
+ nsresult rv = aRangeToDelete.SetStartAndEnd(
+ mPreformattedLineBreak.ToRawRangeBoundary(),
+ mPreformattedLineBreak.NextPoint().ToRawRangeBoundary());
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "nsRange::SetStartAndEnd() failed");
+ return rv;
+ }
+
+ if (preserveEndBoundary) {
+ // FIXME: If the range ends at end of an inline element, we may need to
+ // extend the range.
+ IgnoredErrorResult error;
+ aRangeToDelete.SetStart(
+ EditorRawDOMPoint(mostDistantInlineAncestor).ToRawRangeBoundary(),
+ error);
+ MOZ_ASSERT_IF(!error.Failed(), !aRangeToDelete.Collapsed());
+ NS_WARNING_ASSERTION(!error.Failed(), "nsRange::SetStart() failed");
+ return error.StealNSResult();
+ }
+
IgnoredErrorResult error;
- aRangeToDelete.SelectNode(*mBRElement, error);
+ aRangeToDelete.SelectNode(*mostDistantInlineAncestor, error);
NS_WARNING_ASSERTION(!error.Failed(), "nsRange::SelectNode() failed");
return error.StealNSResult();
}
-Result<EditActionResult, nsresult>
-HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::DeleteBRElement(
- HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount,
- const Element& aEditingHost) {
+Result<EditActionResult, nsresult> HTMLEditor::AutoDeleteRangesHandler::
+ AutoBlockElementsJoiner::HandleDeleteLineBreak(
+ HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount,
+ const EditorDOMPoint& aCaretPoint, const Element& aEditingHost) {
MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable());
- MOZ_ASSERT(mBRElement);
+ MOZ_ASSERT(mBRElement || mPreformattedLineBreak.IsSet());
// If we're deleting selection (not replacing with new content), we should
// put caret to end of preceding text node if there is. Then, users can type
// text in it like the other browsers.
EditorDOMPoint pointToPutCaret = [&]() {
+ // but when we're deleting a preceding line break of current block, we
+ // should keep the caret position in the current block.
+ if (mMode == Mode::DeletePrecedingBRElementOfBlock ||
+ mMode == Mode::DeletePrecedingPreformattedLineBreak) {
+ return aCaretPoint;
+ }
if (!MayEditActionDeleteAroundCollapsedSelection(
aHTMLEditor.GetEditAction())) {
return EditorDOMPoint();
}
WSRunScanner scanner(&aEditingHost, EditorRawDOMPoint(mBRElement),
BlockInlineCheck::UseComputedDisplayOutsideStyle);
- WSScanResult maybePreviousText =
+ const WSScanResult maybePreviousText =
scanner.ScanPreviousVisibleNodeOrBlockBoundaryFrom(
EditorRawDOMPoint(mBRElement));
if (maybePreviousText.IsContentEditable() &&
maybePreviousText.InVisibleOrCollapsibleCharacters() &&
!HTMLEditor::GetLinkElement(maybePreviousText.TextPtr())) {
- return maybePreviousText.Point<EditorDOMPoint>();
+ return maybePreviousText.PointAfterReachedContent<EditorDOMPoint>();
}
- WSScanResult maybeNextText = scanner.ScanNextVisibleNodeOrBlockBoundaryFrom(
- EditorRawDOMPoint::After(*mBRElement));
+ const WSScanResult maybeNextText =
+ scanner.ScanInclusiveNextVisibleNodeOrBlockBoundaryFrom(
+ EditorRawDOMPoint::After(*mBRElement));
if (maybeNextText.IsContentEditable() &&
maybeNextText.InVisibleOrCollapsibleCharacters()) {
- return maybeNextText.Point<EditorDOMPoint>();
+ return maybeNextText.PointAtReachedContent<EditorDOMPoint>();
}
return EditorDOMPoint();
}();
- // If we found a `<br>` element, we should delete it instead of joining the
- // contents.
+ RefPtr<nsRange> rangeToDelete =
+ nsRange::Create(const_cast<Element*>(&aEditingHost));
+ MOZ_ASSERT(rangeToDelete);
nsresult rv =
- aHTMLEditor.DeleteNodeWithTransaction(MOZ_KnownLive(*mBRElement));
+ ComputeRangeToDeleteLineBreak(aHTMLEditor, *rangeToDelete, aEditingHost,
+ ComputeRangeFor::ToDeleteTheRange);
if (NS_FAILED(rv)) {
- NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
+ NS_WARNING(
+ "AutoBlockElementsJoiner::ComputeRangeToDeleteLineBreak() failed");
return Err(rv);
}
+ Result<EditActionResult, nsresult> result = HandleDeleteNonCollapsedRange(
+ aHTMLEditor, aDirectionAndAmount, nsIEditor::eNoStrip, *rangeToDelete,
+ SelectionWasCollapsed::Yes, aEditingHost);
+ if (MOZ_UNLIKELY(result.isErr())) {
+ NS_WARNING(
+ "AutoBlockElementsJoiner::HandleDeleteNonCollapsedRange() failed");
+ return result;
+ }
if (mLeftContent && mRightContent &&
HTMLEditUtils::GetInclusiveAncestorAnyTableElement(*mLeftContent) !=
@@ -2597,7 +2880,7 @@ HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::DeleteBRElement(
}
// Put selection at edge of block and we are done.
- if (NS_WARN_IF(!mLeafContentInOtherBlock)) {
+ if (NS_WARN_IF(mMode == Mode::DeleteBRElement && !mLeafContentInOtherBlock)) {
// XXX This must be odd case. The other block can be empty.
return Err(NS_ERROR_FAILURE);
}
@@ -2607,7 +2890,7 @@ HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::DeleteBRElement(
if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
return Err(NS_ERROR_EDITOR_DESTROYED);
}
- if (NS_SUCCEEDED(rv)) {
+ if (mMode == Mode::DeleteBRElement && NS_SUCCEEDED(rv)) {
// If we prefer to use style in the previous line, we should forget
// previous styles since the caret position has all styles which we want
// to use with new content.
@@ -2622,7 +2905,9 @@ HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::DeleteBRElement(
->ClearLinkAndItsSpecifiedStyle();
}
} else {
- NS_WARNING("EditorBase::CollapseSelectionTo() failed, but ignored");
+ NS_WARNING_ASSERTION(
+ NS_SUCCEEDED(rv),
+ "EditorBase::CollapseSelectionTo() failed, but ignored");
}
return EditActionResult::HandledResult();
}
@@ -2937,7 +3222,8 @@ bool HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
PrepareToDeleteAtCurrentBlockBoundary(
const HTMLEditor& aHTMLEditor,
nsIEditor::EDirection aDirectionAndAmount,
- Element& aCurrentBlockElement, const EditorDOMPoint& aCaretPoint) {
+ Element& aCurrentBlockElement, const EditorDOMPoint& aCaretPoint,
+ const Element& aEditingHost) {
MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable());
// At edge of our block. Look beside it and see if we can join to an
@@ -2956,20 +3242,15 @@ bool HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
return false;
}
- Element* editingHost = aHTMLEditor.ComputeEditingHost();
- if (NS_WARN_IF(!editingHost)) {
- return false;
- }
-
auto ScanJoinTarget = [&]() -> nsIContent* {
nsIContent* targetContent =
aDirectionAndAmount == nsIEditor::ePrevious
? HTMLEditUtils::GetPreviousContent(
aCurrentBlockElement, {WalkTreeOption::IgnoreNonEditableNode},
- BlockInlineCheck::Unused, editingHost)
+ BlockInlineCheck::Unused, &aEditingHost)
: HTMLEditUtils::GetNextContent(
aCurrentBlockElement, {WalkTreeOption::IgnoreNonEditableNode},
- BlockInlineCheck::Unused, editingHost);
+ BlockInlineCheck::Unused, &aEditingHost);
// If found content is an invisible text node, let's scan visible things.
auto IsIgnorableDataNode = [](nsIContent* aContent) {
return aContent && HTMLEditUtils::IsRemovableNode(*aContent) &&
@@ -2987,22 +3268,22 @@ bool HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
? HTMLEditUtils::GetPreviousContent(
*targetContent, {WalkTreeOption::StopAtBlockBoundary},
BlockInlineCheck::UseComputedDisplayOutsideStyle,
- editingHost)
+ &aEditingHost)
: HTMLEditUtils::GetNextContent(
*targetContent, {WalkTreeOption::StopAtBlockBoundary},
BlockInlineCheck::UseComputedDisplayOutsideStyle,
- editingHost);
+ &aEditingHost);
adjacentContent;
adjacentContent =
aDirectionAndAmount == nsIEditor::ePrevious
? HTMLEditUtils::GetPreviousContent(
*adjacentContent, {WalkTreeOption::StopAtBlockBoundary},
BlockInlineCheck::UseComputedDisplayOutsideStyle,
- editingHost)
+ &aEditingHost)
: HTMLEditUtils::GetNextContent(
*adjacentContent, {WalkTreeOption::StopAtBlockBoundary},
BlockInlineCheck::UseComputedDisplayOutsideStyle,
- editingHost)) {
+ &aEditingHost)) {
// If non-editable element is found, we should not skip it to avoid
// joining too far nodes.
if (!HTMLEditUtils::IsSimplyEditableNode(*adjacentContent)) {
@@ -3036,6 +3317,77 @@ bool HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
};
if (aDirectionAndAmount == nsIEditor::ePrevious) {
+ const WSScanResult prevVisibleThing = [&]() {
+ // When Backspace at start of a block, we need to delete only a preceding
+ // <br> element if there is.
+ const Result<Element*, nsresult>
+ inclusiveAncestorOfRightChildBlockOrError = AutoBlockElementsJoiner::
+ GetMostDistantBlockAncestorIfPointIsStartAtBlock(aCaretPoint,
+ aEditingHost);
+ if (NS_WARN_IF(inclusiveAncestorOfRightChildBlockOrError.isErr()) ||
+ !inclusiveAncestorOfRightChildBlockOrError.inspect()) {
+ return WSScanResult::Error();
+ }
+ const WSScanResult prevVisibleThingBeforeCurrentBlock =
+ WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary(
+ &aEditingHost,
+ EditorRawDOMPoint(
+ inclusiveAncestorOfRightChildBlockOrError.inspect()),
+ BlockInlineCheck::UseComputedDisplayOutsideStyle);
+ if (!prevVisibleThingBeforeCurrentBlock.ReachedBRElement() &&
+ !prevVisibleThingBeforeCurrentBlock.ReachedPreformattedLineBreak()) {
+ return WSScanResult::Error();
+ }
+ // There is a preceding line break, but it may be invisible. Then, users
+ // want to delete its preceding content not only the line break.
+ // Therefore, let's check whether the line break follows another line
+ // break or a block boundary. In these cases, the line break causes an
+ // empty line which users may want to delete.
+ const auto atPrecedingLineBreak =
+ prevVisibleThingBeforeCurrentBlock
+ .PointAtReachedContent<EditorRawDOMPoint>();
+ MOZ_ASSERT(atPrecedingLineBreak.IsSet());
+ const WSScanResult prevVisibleThingBeforeLineBreak =
+ WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary(
+ &aEditingHost, atPrecedingLineBreak,
+ BlockInlineCheck::UseComputedDisplayOutsideStyle);
+ if (prevVisibleThingBeforeLineBreak.ReachedBRElement() ||
+ prevVisibleThingBeforeLineBreak.ReachedPreformattedLineBreak() ||
+ prevVisibleThingBeforeLineBreak.ReachedCurrentBlockBoundary()) {
+ // Target the latter line break for things simpler. It's easier to
+ // compute the target range.
+ MOZ_ASSERT_IF(
+ prevVisibleThingBeforeCurrentBlock.ReachedPreformattedLineBreak() &&
+ prevVisibleThingBeforeLineBreak.ReachedPreformattedLineBreak(),
+ prevVisibleThingBeforeCurrentBlock
+ .PointAtReachedContent<EditorRawDOMPoint>() !=
+ prevVisibleThingBeforeLineBreak
+ .PointAtReachedContent<EditorRawDOMPoint>());
+ return prevVisibleThingBeforeCurrentBlock;
+ }
+ return WSScanResult::Error();
+ }();
+
+ // If previous visible thing is a <br>, we should just delete it without
+ // unwrapping the first line of the right child block. Note that the <br>
+ // is always treated as invisible by HTMLEditUtils because it's immediately
+ // preceding <br> of the block boundary. However, deleting it is fine
+ // because the above checks whether it causes empty line or not.
+ if (prevVisibleThing.ReachedBRElement()) {
+ mMode = Mode::DeletePrecedingBRElementOfBlock;
+ mBRElement = prevVisibleThing.BRElementPtr();
+ return true;
+ }
+
+ // Same for a preformatted line break.
+ if (prevVisibleThing.ReachedPreformattedLineBreak()) {
+ mMode = Mode::DeletePrecedingPreformattedLineBreak;
+ mPreformattedLineBreak =
+ prevVisibleThing.PointAtReachedContent<EditorRawDOMPoint>()
+ .AsInText();
+ return true;
+ }
+
mLeftContent = ScanJoinTarget();
mRightContent = aCaretPoint.GetContainerAs<nsIContent>();
} else {
@@ -3283,7 +3635,8 @@ HTMLEditor::AutoDeleteRangesHandler::ComputeRangesToDeleteNonCollapsedRanges(
continue;
}
AutoBlockElementsJoiner joiner(*this);
- if (!joiner.PrepareToDeleteNonCollapsedRange(aHTMLEditor, range)) {
+ if (!joiner.PrepareToDeleteNonCollapsedRange(aHTMLEditor, range,
+ aEditingHost)) {
return NS_ERROR_FAILURE;
}
nsresult rv =
@@ -3473,7 +3826,8 @@ HTMLEditor::AutoDeleteRangesHandler::HandleDeleteNonCollapsedRanges(
continue;
}
AutoBlockElementsJoiner joiner(*this);
- if (!joiner.PrepareToDeleteNonCollapsedRange(aHTMLEditor, range)) {
+ if (!joiner.PrepareToDeleteNonCollapsedRange(aHTMLEditor, range,
+ aEditingHost)) {
return Err(NS_ERROR_FAILURE);
}
Result<EditActionResult, nsresult> result =
@@ -3490,7 +3844,8 @@ HTMLEditor::AutoDeleteRangesHandler::HandleDeleteNonCollapsedRanges(
bool HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
PrepareToDeleteNonCollapsedRange(const HTMLEditor& aHTMLEditor,
- const nsRange& aRangeToDelete) {
+ const nsRange& aRangeToDelete,
+ const Element& aEditingHost) {
MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable());
MOZ_ASSERT(!aRangeToDelete.Collapsed());
@@ -3528,6 +3883,125 @@ bool HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
return true;
}
+ // If the range starts immediately after a line end and ends in a
+ // child right block, we should not unwrap the right block unless the
+ // right block will have no nodes.
+ if (mRightContent->IsInclusiveDescendantOf(mLeftContent)) {
+ // FYI: Chrome does not remove the right child block even if there will be
+ // only single <br> or a comment node in it. Therefore, we should use this
+ // rough check.
+ const WSScanResult nextVisibleThingOfEndBoundary =
+ WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary(
+ &aEditingHost, EditorRawDOMPoint(aRangeToDelete.EndRef()),
+ BlockInlineCheck::UseComputedDisplayOutsideStyle);
+ if (!nextVisibleThingOfEndBoundary.ReachedCurrentBlockBoundary()) {
+ MOZ_ASSERT(mLeftContent->IsElement());
+ Result<Element*, nsresult> mostDistantBlockOrError =
+ AutoBlockElementsJoiner::
+ GetMostDistantBlockAncestorIfPointIsStartAtBlock(
+ EditorRawDOMPoint(mRightContent, 0), aEditingHost,
+ mLeftContent->AsElement());
+ MOZ_ASSERT(mostDistantBlockOrError.isOk());
+ if (MOZ_LIKELY(mostDistantBlockOrError.inspect())) {
+ const WSScanResult prevVisibleThingOfStartBoundary =
+ WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary(
+ &aEditingHost, EditorRawDOMPoint(aRangeToDelete.StartRef()),
+ BlockInlineCheck::UseComputedDisplayOutsideStyle);
+ if (prevVisibleThingOfStartBoundary.ReachedBRElement()) {
+ // If the range start after a <br> followed by the block boundary,
+ // we want to delete the <br> or following <br> element unless it's
+ // not a part of empty line like `<div>abc<br>{<div>]def`.
+ const WSScanResult nextVisibleThingOfBR =
+ WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary(
+ &aEditingHost,
+ EditorRawDOMPoint::After(
+ *prevVisibleThingOfStartBoundary.GetContent()),
+ BlockInlineCheck::UseComputedDisplayOutsideStyle);
+ MOZ_ASSERT(!nextVisibleThingOfBR.ReachedCurrentBlockBoundary());
+ if (!nextVisibleThingOfBR.ReachedOtherBlockElement() ||
+ nextVisibleThingOfBR.GetContent() !=
+ mostDistantBlockOrError.inspect()) {
+ // The range selects a non-empty line or a child block at least.
+ mMode = Mode::DeletePrecedingLinesAndContentInRange;
+ return true;
+ }
+ const WSScanResult prevVisibleThingOfBR =
+ WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary(
+ &aEditingHost,
+ EditorRawDOMPoint(
+ prevVisibleThingOfStartBoundary.GetContent()),
+ BlockInlineCheck::UseComputedDisplayOutsideStyle);
+ if (prevVisibleThingOfBR.ReachedBRElement() ||
+ prevVisibleThingOfBR.ReachedPreformattedLineBreak() ||
+ prevVisibleThingOfBR.ReachedBlockBoundary()) {
+ // The preceding <br> causes an empty line.
+ mMode = Mode::DeletePrecedingLinesAndContentInRange;
+ return true;
+ }
+ } else if (prevVisibleThingOfStartBoundary
+ .ReachedPreformattedLineBreak()) {
+ const WSScanResult nextVisibleThingOfLineBreak =
+ WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary(
+ &aEditingHost,
+ prevVisibleThingOfStartBoundary
+ .PointAfterReachedContent<EditorRawDOMPoint>(),
+ BlockInlineCheck::UseComputedDisplayOutsideStyle);
+ MOZ_ASSERT(
+ !nextVisibleThingOfLineBreak.ReachedCurrentBlockBoundary());
+ if (!nextVisibleThingOfLineBreak.ReachedOtherBlockElement() ||
+ nextVisibleThingOfLineBreak.GetContent() !=
+ mostDistantBlockOrError.inspect()) {
+ // The range selects a non-empty line or a child block at least.
+ mMode = Mode::DeletePrecedingLinesAndContentInRange;
+ return true;
+ }
+ const WSScanResult prevVisibleThingOfLineBreak =
+ WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary(
+ &aEditingHost,
+ prevVisibleThingOfStartBoundary
+ .PointAtReachedContent<EditorRawDOMPoint>(),
+ BlockInlineCheck::UseComputedDisplayOutsideStyle);
+ if (prevVisibleThingOfLineBreak.ReachedBRElement() ||
+ prevVisibleThingOfLineBreak.ReachedPreformattedLineBreak() ||
+ prevVisibleThingOfLineBreak.ReachedBlockBoundary()) {
+ // The preceding line break causes an empty line.
+ mMode = Mode::DeletePrecedingLinesAndContentInRange;
+ return true;
+ }
+ } else if (prevVisibleThingOfStartBoundary
+ .ReachedCurrentBlockBoundary()) {
+ MOZ_ASSERT(prevVisibleThingOfStartBoundary.ElementPtr() ==
+ mLeftContent);
+ const WSScanResult firstVisibleThingInBlock =
+ WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary(
+ &aEditingHost,
+ EditorRawDOMPoint(
+ prevVisibleThingOfStartBoundary.ElementPtr(), 0),
+ BlockInlineCheck::UseComputedDisplayOutsideStyle);
+ if (!firstVisibleThingInBlock.ReachedOtherBlockElement() ||
+ firstVisibleThingInBlock.ElementPtr() !=
+ mostDistantBlockOrError.inspect()) {
+ mMode = Mode::DeletePrecedingLinesAndContentInRange;
+ return true;
+ }
+ } else if (prevVisibleThingOfStartBoundary.ReachedOtherBlockElement()) {
+ const WSScanResult firstVisibleThingAfterBlock =
+ WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary(
+ &aEditingHost,
+ EditorRawDOMPoint::After(
+ *prevVisibleThingOfStartBoundary.ElementPtr()),
+ BlockInlineCheck::UseComputedDisplayOutsideStyle);
+ if (!firstVisibleThingAfterBlock.ReachedOtherBlockElement() ||
+ firstVisibleThingAfterBlock.ElementPtr() !=
+ mostDistantBlockOrError.inspect()) {
+ mMode = Mode::DeletePrecedingLinesAndContentInRange;
+ return true;
+ }
+ }
+ }
+ }
+ }
+
mMode = Mode::DeleteNonCollapsedRange;
return true;
}
@@ -3744,12 +4218,12 @@ Result<EditActionResult, nsresult> HTMLEditor::AutoDeleteRangesHandler::
aHTMLEditor.GetEditAction())) {
WSRunScanner scanner(&aEditingHost, startOfRightContent,
BlockInlineCheck::UseComputedDisplayOutsideStyle);
- WSScanResult maybePreviousText =
+ const WSScanResult maybePreviousText =
scanner.ScanPreviousVisibleNodeOrBlockBoundaryFrom(startOfRightContent);
if (maybePreviousText.IsContentEditable() &&
maybePreviousText.InVisibleOrCollapsibleCharacters()) {
nsresult rv = aHTMLEditor.CollapseSelectionTo(
- maybePreviousText.Point<EditorRawDOMPoint>());
+ maybePreviousText.PointAfterReachedContent<EditorRawDOMPoint>());
if (NS_FAILED(rv)) {
NS_WARNING("EditorBase::CollapseSelectionTo() failed");
return Err(rv);
@@ -3851,6 +4325,16 @@ bool HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
const nsTArray<OwningNonNull<nsIContent>>& aArrayOfContents,
AutoDeleteRangesHandler::SelectionWasCollapsed aSelectionWasCollapsed)
const {
+ switch (mMode) {
+ case Mode::DeletePrecedingLinesAndContentInRange:
+ case Mode::DeleteBRElement:
+ case Mode::DeletePrecedingBRElementOfBlock:
+ case Mode::DeletePrecedingPreformattedLineBreak:
+ return false;
+ default:
+ break;
+ }
+
// If original selection was collapsed, we need always to join the nodes.
// XXX Why?
if (aSelectionWasCollapsed ==
@@ -3890,41 +4374,103 @@ nsresult HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
DeleteTextAtStartAndEndOfRange(HTMLEditor& aHTMLEditor, nsRange& aRange) {
EditorDOMPoint rangeStart(aRange.StartRef());
EditorDOMPoint rangeEnd(aRange.EndRef());
- if (rangeStart.IsInTextNode() && !rangeStart.IsEndOfContainer()) {
- // Delete to last character
+ if (MOZ_UNLIKELY(aRange.Collapsed())) {
+ return NS_OK;
+ }
+
+ EditorDOMPoint pointToPutCaret;
+ // If the range is in a text node, delete middle of the text or the text node
+ // itself.
+ if (rangeStart.IsInTextNode() &&
+ rangeStart.ContainerAs<Text>() == rangeEnd.GetContainer()) {
OwningNonNull<Text> textNode = *rangeStart.ContainerAs<Text>();
- Result<CaretPoint, nsresult> caretPointOrError =
- aHTMLEditor.DeleteTextWithTransaction(
- textNode, rangeStart.Offset(),
- rangeStart.GetContainer()->Length() - rangeStart.Offset());
- if (MOZ_UNLIKELY(caretPointOrError.isErr())) {
- NS_WARNING("HTMLEditor::DeleteTextWithTransaction() failed");
- return caretPointOrError.unwrapErr();
+ if (rangeStart.IsStartOfContainer() && rangeEnd.IsEndOfContainer()) {
+ EditorDOMPoint pointToPutCaret(textNode);
+ AutoTrackDOMPoint trackTextNodePoint(aHTMLEditor.RangeUpdaterRef(),
+ &pointToPutCaret);
+ nsresult rv = aHTMLEditor.DeleteNodeWithTransaction(textNode);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
+ return rv;
+ }
+ } else {
+ MOZ_ASSERT(rangeEnd.Offset() - rangeStart.Offset() > 0);
+ Result<CaretPoint, nsresult> caretPointOrError =
+ aHTMLEditor.DeleteTextWithTransaction(
+ textNode, rangeStart.Offset(),
+ rangeEnd.Offset() - rangeStart.Offset());
+ if (MOZ_UNLIKELY(caretPointOrError.isErr())) {
+ NS_WARNING("HTMLEditor::DeleteTextWithTransaction() failed");
+ return caretPointOrError.unwrapErr();
+ }
+ caretPointOrError.unwrap().MoveCaretPointTo(
+ pointToPutCaret, aHTMLEditor,
+ {SuggestCaret::OnlyIfHasSuggestion,
+ SuggestCaret::OnlyIfTransactionsAllowedToDoIt});
}
- nsresult rv = caretPointOrError.inspect().SuggestCaretPointTo(
- aHTMLEditor, {SuggestCaret::OnlyIfHasSuggestion,
- SuggestCaret::OnlyIfTransactionsAllowedToDoIt,
- SuggestCaret::AndIgnoreTrivialError});
- if (NS_FAILED(rv)) {
- NS_WARNING("CaretPoint::SuggestCaretPointTo() failed");
- return rv;
+ } else {
+ // If the range starts in a text node and ends in a different node, delete
+ // the text after the start boundary.
+ if (rangeStart.IsInTextNode() && !rangeStart.IsEndOfContainer()) {
+ OwningNonNull<Text> textNode = *rangeStart.ContainerAs<Text>();
+ if (rangeStart.IsStartOfContainer()) {
+ pointToPutCaret.Set(textNode);
+ AutoTrackDOMPoint trackTextNodePoint(aHTMLEditor.RangeUpdaterRef(),
+ &pointToPutCaret);
+ nsresult rv = aHTMLEditor.DeleteNodeWithTransaction(textNode);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
+ return rv;
+ }
+ } else {
+ Result<CaretPoint, nsresult> caretPointOrError =
+ aHTMLEditor.DeleteTextWithTransaction(
+ textNode, rangeStart.Offset(),
+ rangeStart.GetContainer()->Length() - rangeStart.Offset());
+ if (MOZ_UNLIKELY(caretPointOrError.isErr())) {
+ NS_WARNING("HTMLEditor::DeleteTextWithTransaction() failed");
+ return caretPointOrError.unwrapErr();
+ }
+ caretPointOrError.unwrap().MoveCaretPointTo(
+ pointToPutCaret, aHTMLEditor,
+ {SuggestCaret::OnlyIfHasSuggestion,
+ SuggestCaret::OnlyIfTransactionsAllowedToDoIt});
+ }
}
- NS_WARNING_ASSERTION(
- rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
- "CaretPoint::SuggestCaretPointTo() failed, but ignored");
- }
- if (rangeEnd.IsInTextNode() && !rangeEnd.IsStartOfContainer()) {
- // Delete to first character
- OwningNonNull<Text> textNode = *rangeEnd.ContainerAs<Text>();
- Result<CaretPoint, nsresult> caretPointOrError =
- aHTMLEditor.DeleteTextWithTransaction(textNode, 0, rangeEnd.Offset());
- if (MOZ_UNLIKELY(caretPointOrError.isErr())) {
- NS_WARNING("HTMLEditor::DeleteTextWithTransaction() failed");
- return caretPointOrError.unwrapErr();
+
+ // If the range ends in a text node and starts from a different node, delete
+ // the text before the end boundary.
+ if (rangeEnd.IsInTextNode() && !rangeEnd.IsStartOfContainer()) {
+ OwningNonNull<Text> textNode = *rangeEnd.ContainerAs<Text>();
+ if (rangeEnd.IsEndOfContainer()) {
+ pointToPutCaret.Set(textNode);
+ AutoTrackDOMPoint trackTextNodePoint(aHTMLEditor.RangeUpdaterRef(),
+ &pointToPutCaret);
+ nsresult rv = aHTMLEditor.DeleteNodeWithTransaction(textNode);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
+ return rv;
+ }
+ } else {
+ Result<CaretPoint, nsresult> caretPointOrError =
+ aHTMLEditor.DeleteTextWithTransaction(textNode, 0,
+ rangeEnd.Offset());
+ if (MOZ_UNLIKELY(caretPointOrError.isErr())) {
+ NS_WARNING("HTMLEditor::DeleteTextWithTransaction() failed");
+ return caretPointOrError.unwrapErr();
+ }
+ caretPointOrError.unwrap().MoveCaretPointTo(
+ pointToPutCaret, aHTMLEditor,
+ {SuggestCaret::OnlyIfHasSuggestion,
+ SuggestCaret::OnlyIfTransactionsAllowedToDoIt});
+ }
}
- nsresult rv = caretPointOrError.inspect().SuggestCaretPointTo(
- aHTMLEditor, {SuggestCaret::OnlyIfHasSuggestion,
- SuggestCaret::OnlyIfTransactionsAllowedToDoIt,
+ }
+
+ if (pointToPutCaret.IsSet()) {
+ CaretPoint caretPoint(std::move(pointToPutCaret));
+ nsresult rv = caretPoint.SuggestCaretPointTo(
+ aHTMLEditor, {SuggestCaret::OnlyIfTransactionsAllowedToDoIt,
SuggestCaret::AndIgnoreTrivialError});
if (NS_FAILED(rv)) {
NS_WARNING("CaretPoint::SuggestCaretPointTo() failed");
@@ -3937,6 +4483,201 @@ nsresult HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
return NS_OK;
}
+// static
+template <typename EditorDOMPointType>
+Result<Element*, nsresult> HTMLEditor::AutoDeleteRangesHandler::
+ AutoBlockElementsJoiner::GetMostDistantBlockAncestorIfPointIsStartAtBlock(
+ const EditorDOMPointType& aPoint, const Element& aEditingHost,
+ const Element* aAncestorLimiter /* = nullptr */) {
+ MOZ_ASSERT(aPoint.IsSetAndValid());
+ MOZ_ASSERT(aPoint.IsInComposedDoc());
+
+ if (!aAncestorLimiter) {
+ aAncestorLimiter = &aEditingHost;
+ }
+
+ const auto ReachedCurrentBlockBoundaryWhichWeCanCross =
+ [&aEditingHost, aAncestorLimiter](const WSScanResult& aScanResult) {
+ // When the scan result is "reached current block boundary", it may not
+ // be so literally.
+ return aScanResult.ReachedCurrentBlockBoundary() &&
+ HTMLEditUtils::IsRemovableFromParentNode(
+ *aScanResult.ElementPtr()) &&
+ aScanResult.ElementPtr() != &aEditingHost &&
+ aScanResult.ElementPtr() != aAncestorLimiter &&
+ // Don't cross <body>, <head> and <html>
+ !aScanResult.ElementPtr()->IsAnyOfHTMLElements(
+ nsGkAtoms::body, nsGkAtoms::head, nsGkAtoms::html) &&
+ // Don't cross table elements
+ !HTMLEditUtils::IsAnyTableElement(aScanResult.ElementPtr());
+ };
+
+ const WSScanResult prevVisibleThing =
+ WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary(
+ aAncestorLimiter, aPoint,
+ BlockInlineCheck::UseComputedDisplayOutsideStyle);
+ if (!ReachedCurrentBlockBoundaryWhichWeCanCross(prevVisibleThing)) {
+ return nullptr;
+ }
+ MOZ_ASSERT(HTMLEditUtils::IsBlockElement(
+ *prevVisibleThing.ElementPtr(),
+ BlockInlineCheck::UseComputedDisplayOutsideStyle));
+ for (Element* ancestorBlock = prevVisibleThing.ElementPtr(); ancestorBlock;) {
+ const WSScanResult prevVisibleThing =
+ WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary(
+ aAncestorLimiter, EditorRawDOMPoint(ancestorBlock),
+ BlockInlineCheck::UseComputedDisplayOutsideStyle);
+ if (!ReachedCurrentBlockBoundaryWhichWeCanCross(prevVisibleThing)) {
+ return ancestorBlock;
+ }
+ MOZ_ASSERT(HTMLEditUtils::IsBlockElement(
+ *prevVisibleThing.ElementPtr(),
+ BlockInlineCheck::UseComputedDisplayOutsideStyle));
+ ancestorBlock = prevVisibleThing.ElementPtr();
+ }
+ return Err(NS_ERROR_FAILURE);
+}
+
+void HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
+ ExtendRangeToDeleteNonCollapsedRange(
+ const HTMLEditor& aHTMLEditor, nsRange& aRangeToDelete,
+ const Element& aEditingHost, ComputeRangeFor aComputeRangeFor) const {
+ MOZ_ASSERT_IF(aComputeRangeFor == ComputeRangeFor::GetTargetRanges,
+ aRangeToDelete.IsPositioned());
+ MOZ_ASSERT(!aRangeToDelete.Collapsed());
+ MOZ_ASSERT(mLeftContent);
+ MOZ_ASSERT(mLeftContent->IsElement());
+ MOZ_ASSERT(aRangeToDelete.GetStartContainer()->IsInclusiveDescendantOf(
+ mLeftContent));
+ MOZ_ASSERT(mRightContent);
+ MOZ_ASSERT(mRightContent->IsElement());
+ MOZ_ASSERT(
+ aRangeToDelete.GetEndContainer()->IsInclusiveDescendantOf(mRightContent));
+
+ const DebugOnly<Result<bool, nsresult>> extendRangeResult =
+ AutoDeleteRangesHandler::
+ ExtendRangeToContainAncestorInlineElementsAtStart(aRangeToDelete,
+ aEditingHost);
+ NS_WARNING_ASSERTION(extendRangeResult.value.isOk(),
+ "AutoDeleteRangesHandler::"
+ "ExtendRangeToContainAncestorInlineElementsAtStart() "
+ "failed, but ignored");
+ if (mMode != Mode::DeletePrecedingLinesAndContentInRange) {
+ return;
+ }
+
+ // If we're computing for beforeinput.getTargetRanges() and the inputType
+ // is not a simple deletion like replacing selected content with new
+ // content, the range should end at the original end boundary of the given
+ // range even if we're deleting only preceding lines of the right child
+ // block.
+ const bool preserveEndBoundary =
+ aComputeRangeFor == ComputeRangeFor::GetTargetRanges &&
+ !MayEditActionDeleteAroundCollapsedSelection(aHTMLEditor.GetEditAction());
+ // We need to delete only the preceding lines of the right block. Therefore,
+ // we need to shrink the range to ends before the right block if the range
+ // does not contain any meaningful content in the right block.
+ const Result<Element*, nsresult> inclusiveAncestorCurrentBlockOrError =
+ AutoBlockElementsJoiner::GetMostDistantBlockAncestorIfPointIsStartAtBlock(
+ EditorRawDOMPoint(aRangeToDelete.EndRef()), aEditingHost,
+ mLeftContent->AsElement());
+ MOZ_ASSERT(inclusiveAncestorCurrentBlockOrError.isOk());
+ MOZ_ASSERT_IF(inclusiveAncestorCurrentBlockOrError.inspect(),
+ mRightContent->IsInclusiveDescendantOf(
+ inclusiveAncestorCurrentBlockOrError.inspect()));
+ if (MOZ_UNLIKELY(!inclusiveAncestorCurrentBlockOrError.isOk() ||
+ !inclusiveAncestorCurrentBlockOrError.inspect())) {
+ return;
+ }
+
+ const WSScanResult prevVisibleThingOfStartBoundary =
+ WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary(
+ &aEditingHost, EditorRawDOMPoint(aRangeToDelete.StartRef()),
+ BlockInlineCheck::UseComputedDisplayOutsideStyle);
+ // If the range starts after an invisible <br> of empty line immediately
+ // before the most distant inclusive ancestor of the right block like
+ // `<br><br>{<div>]abc`, we should delete the last empty line because
+ // users won't see any reaction of the builtin editor in this case.
+ if (prevVisibleThingOfStartBoundary.ReachedBRElement() ||
+ prevVisibleThingOfStartBoundary.ReachedPreformattedLineBreak()) {
+ const WSScanResult prevVisibleThingOfPreviousLineBreak =
+ WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary(
+ &aEditingHost,
+ prevVisibleThingOfStartBoundary
+ .PointAtReachedContent<EditorRawDOMPoint>(),
+ BlockInlineCheck::UseComputedDisplayOutsideStyle);
+ const WSScanResult nextVisibleThingOfPreviousBR =
+ WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary(
+ &aEditingHost,
+ prevVisibleThingOfStartBoundary
+ .PointAfterReachedContent<EditorRawDOMPoint>(),
+ BlockInlineCheck::UseComputedDisplayOutsideStyle);
+ if ((prevVisibleThingOfPreviousLineBreak.ReachedBRElement() ||
+ prevVisibleThingOfPreviousLineBreak.ReachedPreformattedLineBreak()) &&
+ nextVisibleThingOfPreviousBR.ReachedOtherBlockElement() &&
+ nextVisibleThingOfPreviousBR.ElementPtr() ==
+ inclusiveAncestorCurrentBlockOrError.inspect()) {
+ aRangeToDelete.SetStart(prevVisibleThingOfStartBoundary
+ .PointAtReachedContent<EditorRawDOMPoint>()
+ .ToRawRangeBoundary(),
+ IgnoreErrors());
+ }
+ }
+
+ if (preserveEndBoundary) {
+ return;
+ }
+
+ if (aComputeRangeFor == ComputeRangeFor::GetTargetRanges) {
+ // When we set the end boundary to around the right block, the new end
+ // boundary should not after inline ancestors of the line break which won't
+ // be deleted.
+ const WSScanResult lastVisibleThingBeforeRightChildBlock =
+ [&]() -> WSScanResult {
+ EditorRawDOMPoint scanStartPoint(aRangeToDelete.StartRef());
+ WSScanResult lastScanResult = WSScanResult::Error();
+ while (true) {
+ WSScanResult scanResult =
+ WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary(
+ mLeftContent->AsElement(), scanStartPoint,
+ BlockInlineCheck::UseComputedDisplayOutsideStyle);
+ if (scanResult.ReachedBlockBoundary() ||
+ scanResult.ReachedInlineEditingHostBoundary()) {
+ return lastScanResult;
+ }
+ scanStartPoint =
+ scanResult.PointAfterReachedContent<EditorRawDOMPoint>();
+ lastScanResult = scanResult;
+ }
+ }();
+ if (lastVisibleThingBeforeRightChildBlock.GetContent()) {
+ const nsIContent* commonAncestor = nsIContent::FromNode(
+ nsContentUtils::GetClosestCommonInclusiveAncestor(
+ aRangeToDelete.StartRef().Container(),
+ lastVisibleThingBeforeRightChildBlock.GetContent()));
+ MOZ_ASSERT(commonAncestor);
+ if (commonAncestor &&
+ !mRightContent->IsInclusiveDescendantOf(commonAncestor)) {
+ IgnoredErrorResult error;
+ aRangeToDelete.SetEnd(
+ EditorRawDOMPoint::AtEndOf(*commonAncestor).ToRawRangeBoundary(),
+ error);
+ NS_WARNING_ASSERTION(!error.Failed(),
+ "nsRange::SetEnd() failed, but ignored");
+ return;
+ }
+ }
+ }
+
+ IgnoredErrorResult error;
+ aRangeToDelete.SetEnd(
+ EditorRawDOMPoint(inclusiveAncestorCurrentBlockOrError.inspect())
+ .ToRawRangeBoundary(),
+ error);
+ NS_WARNING_ASSERTION(!error.Failed(),
+ "nsRange::SetEnd() failed, but ignored");
+}
+
nsresult HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
ComputeRangeToDeleteNonCollapsedRange(
const HTMLEditor& aHTMLEditor,
@@ -3954,6 +4695,10 @@ nsresult HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
MOZ_ASSERT(
aRangeToDelete.GetEndContainer()->IsInclusiveDescendantOf(mRightContent));
+ ExtendRangeToDeleteNonCollapsedRange(aHTMLEditor, aRangeToDelete,
+ aEditingHost,
+ ComputeRangeFor::GetTargetRanges);
+
Result<bool, nsresult> result =
ComputeRangeToDeleteNodesEntirelyInRangeButKeepTableStructure(
aHTMLEditor, aRangeToDelete, aSelectionWasCollapsed);
@@ -4003,14 +4748,20 @@ Result<EditActionResult, nsresult> HTMLEditor::AutoDeleteRangesHandler::
MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable());
MOZ_ASSERT(!aRangeToDelete.Collapsed());
MOZ_ASSERT(mDeleteRangesHandler);
- MOZ_ASSERT(mLeftContent);
- MOZ_ASSERT(mLeftContent->IsElement());
- MOZ_ASSERT(aRangeToDelete.GetStartContainer()->IsInclusiveDescendantOf(
- mLeftContent));
- MOZ_ASSERT(mRightContent);
- MOZ_ASSERT(mRightContent->IsElement());
- MOZ_ASSERT(
- aRangeToDelete.GetEndContainer()->IsInclusiveDescendantOf(mRightContent));
+
+ const bool isDeletingLineBreak =
+ mMode == Mode::DeleteBRElement ||
+ mMode == Mode::DeletePrecedingBRElementOfBlock ||
+ mMode == Mode::DeletePrecedingPreformattedLineBreak;
+ if (!isDeletingLineBreak) {
+ MOZ_ASSERT(aRangeToDelete.GetStartContainer()->IsInclusiveDescendantOf(
+ mLeftContent));
+ MOZ_ASSERT(aRangeToDelete.GetEndContainer()->IsInclusiveDescendantOf(
+ mRightContent));
+ ExtendRangeToDeleteNonCollapsedRange(aHTMLEditor, aRangeToDelete,
+ aEditingHost,
+ ComputeRangeFor::ToDeleteTheRange);
+ }
const bool backspaceInRightBlock =
aSelectionWasCollapsed == SelectionWasCollapsed::Yes &&
@@ -4034,7 +4785,8 @@ Result<EditActionResult, nsresult> HTMLEditor::AutoDeleteRangesHandler::
return deleteResult.propagateErr();
}
- const bool joinInclusiveAncestorBlockElements = deleteResult.unwrap();
+ const bool joinInclusiveAncestorBlockElements =
+ !isDeletingLineBreak && deleteResult.unwrap();
// Check endpoints for possible text deletion. We can assume that if
// text node is found, we can delete to end or to beginning as
@@ -4048,9 +4800,24 @@ Result<EditActionResult, nsresult> HTMLEditor::AutoDeleteRangesHandler::
}
if (!joinInclusiveAncestorBlockElements) {
+ // When we delete only preceding lines of the right child block, we should
+ // put caret into start of the right block.
+ if (mMode == Mode::DeletePrecedingLinesAndContentInRange) {
+ result.MarkAsHandled();
+ if (MOZ_LIKELY(mRightContent->IsInComposedDoc())) {
+ pointToPutCaret =
+ HTMLEditUtils::GetDeepestEditableStartPointOf<EditorDOMPoint>(
+ *mRightContent);
+ }
+ }
break;
}
+ MOZ_ASSERT(mLeftContent);
+ MOZ_ASSERT(mLeftContent->IsElement());
+ MOZ_ASSERT(mRightContent);
+ MOZ_ASSERT(mRightContent->IsElement());
+
AutoInclusiveAncestorBlockElementsJoiner joiner(*mLeftContent,
*mRightContent);
Result<bool, nsresult> canJoinThem =
@@ -4106,6 +4873,12 @@ Result<EditActionResult, nsresult> HTMLEditor::AutoDeleteRangesHandler::
break;
}
+ // HandleDeleteLineBreak() should handle the new caret position by itself.
+ if (isDeletingLineBreak) {
+ result.MarkAsHandled();
+ return result;
+ }
+
// If we're deleting selection (not replacing with new content) and
// AutoInclusiveAncestorBlockElementsJoiner computed new caret position, we
// should use it. Otherwise, we should keep the traditional behavior.
@@ -4297,7 +5070,8 @@ HTMLEditor::AutoDeleteRangesHandler::DeleteParentBlocksWithTransactionIfEmpty(
RefPtr<Element> editingHost = aHTMLEditor.ComputeEditingHost();
WSRunScanner wsScannerForPoint(
editingHost, aPoint, BlockInlineCheck::UseComputedDisplayOutsideStyle);
- if (!wsScannerForPoint.StartsFromCurrentBlockBoundary()) {
+ if (!wsScannerForPoint.StartsFromCurrentBlockBoundary() &&
+ !wsScannerForPoint.StartsFromInlineEditingHostBoundary()) {
// If there is visible node before the point, we shouldn't remove the
// parent block.
return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND;
@@ -4319,8 +5093,8 @@ HTMLEditor::AutoDeleteRangesHandler::DeleteParentBlocksWithTransactionIfEmpty(
}
// Next, check there is visible contents after the point in current block.
- WSScanResult forwardScanFromPointResult =
- wsScannerForPoint.ScanNextVisibleNodeOrBlockBoundaryFrom(aPoint);
+ const WSScanResult forwardScanFromPointResult =
+ wsScannerForPoint.ScanInclusiveNextVisibleNodeOrBlockBoundaryFrom(aPoint);
if (forwardScanFromPointResult.Failed()) {
NS_WARNING("WSRunScanner::ScanNextVisibleNodeOrBlockBoundaryFrom() failed");
return NS_ERROR_FAILURE;
@@ -4340,8 +5114,8 @@ HTMLEditor::AutoDeleteRangesHandler::DeleteParentBlocksWithTransactionIfEmpty(
return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND;
}
if (wsScannerForPoint.GetEndReasonContent()->GetNextSibling()) {
- WSScanResult scanResult =
- WSRunScanner::ScanNextVisibleNodeOrBlockBoundary(
+ const WSScanResult scanResult =
+ WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary(
editingHost,
EditorRawDOMPoint::After(
*wsScannerForPoint.GetEndReasonContent()),
@@ -4350,13 +5124,15 @@ HTMLEditor::AutoDeleteRangesHandler::DeleteParentBlocksWithTransactionIfEmpty(
NS_WARNING("WSRunScanner::ScanNextVisibleNodeOrBlockBoundary() failed");
return NS_ERROR_FAILURE;
}
- if (!scanResult.ReachedCurrentBlockBoundary()) {
+ if (!scanResult.ReachedCurrentBlockBoundary() &&
+ !scanResult.ReachedInlineEditingHostBoundary()) {
// If we couldn't reach the block's end after the invisible <br>,
// that means that there is visible content.
return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND;
}
}
- } else if (!forwardScanFromPointResult.ReachedCurrentBlockBoundary()) {
+ } else if (!forwardScanFromPointResult.ReachedCurrentBlockBoundary() &&
+ !forwardScanFromPointResult.ReachedInlineEditingHostBoundary()) {
// If we couldn't reach the block's end, the block has visible content.
return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND;
}
@@ -5169,11 +5945,12 @@ Result<EditActionResult, nsresult> HTMLEditor::AutoDeleteRangesHandler::
aHTMLEditor.GetEditAction())) {
WSRunScanner scanner(&aEditingHost, startOfRightContent,
BlockInlineCheck::UseComputedDisplayStyle);
- WSScanResult maybePreviousText =
+ const WSScanResult maybePreviousText =
scanner.ScanPreviousVisibleNodeOrBlockBoundaryFrom(startOfRightContent);
if (maybePreviousText.IsContentEditable() &&
maybePreviousText.InVisibleOrCollapsibleCharacters()) {
- mPointToPutCaret = maybePreviousText.Point<EditorDOMPoint>();
+ mPointToPutCaret =
+ maybePreviousText.PointAfterReachedContent<EditorDOMPoint>();
}
}
return result;
@@ -6798,11 +7575,12 @@ HTMLEditor::AutoDeleteRangesHandler::ExtendOrShrinkRangeToDelete(
if (rangeToDelete.StartRef().GetContainer() !=
closestBlockAncestorOrInlineEditingHost) {
for (;;) {
- WSScanResult backwardScanFromStartResult =
+ const WSScanResult backwardScanFromStartResult =
WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary(
closestEditingHost, rangeToDelete.StartRef(),
BlockInlineCheck::UseComputedDisplayOutsideStyle);
- if (!backwardScanFromStartResult.ReachedCurrentBlockBoundary()) {
+ if (!backwardScanFromStartResult.ReachedCurrentBlockBoundary() &&
+ !backwardScanFromStartResult.ReachedInlineEditingHostBoundary()) {
break;
}
MOZ_ASSERT(backwardScanFromStartResult.GetContent() ==
@@ -6834,8 +7612,8 @@ HTMLEditor::AutoDeleteRangesHandler::ExtendOrShrinkRangeToDelete(
*backwardScanFromStartResult.ElementPtr())) {
break;
}
- rangeToDelete.SetStart(
- backwardScanFromStartResult.PointAtContent<EditorRawDOMPoint>());
+ rangeToDelete.SetStart(backwardScanFromStartResult
+ .PointAtReachedContent<EditorRawDOMPoint>());
}
if (aFrameSelection && !aFrameSelection->IsValidSelectionPoint(
rangeToDelete.StartRef().GetContainer())) {
@@ -6856,8 +7634,8 @@ HTMLEditor::AutoDeleteRangesHandler::ExtendOrShrinkRangeToDelete(
WSRunScanner wsScannerAtEnd(
closestEditingHost, rangeToDelete.EndRef(),
BlockInlineCheck::UseComputedDisplayOutsideStyle);
- WSScanResult forwardScanFromEndResult =
- wsScannerAtEnd.ScanNextVisibleNodeOrBlockBoundaryFrom(
+ const WSScanResult forwardScanFromEndResult =
+ wsScannerAtEnd.ScanInclusiveNextVisibleNodeOrBlockBoundaryFrom(
rangeToDelete.EndRef());
if (forwardScanFromEndResult.ReachedBRElement()) {
// XXX In my understanding, this is odd. The end reason may not be
@@ -6881,7 +7659,9 @@ HTMLEditor::AutoDeleteRangesHandler::ExtendOrShrinkRangeToDelete(
continue;
}
- if (forwardScanFromEndResult.ReachedCurrentBlockBoundary()) {
+ if (forwardScanFromEndResult.ReachedCurrentBlockBoundary() ||
+ forwardScanFromEndResult.ReachedInlineEditingHostBoundary()) {
+ MOZ_ASSERT(forwardScanFromEndResult.ContentIsElement());
MOZ_ASSERT(forwardScanFromEndResult.GetContent() ==
wsScannerAtEnd.GetEndReasonContent());
// We want to keep looking up. But stop if we are crossing table
@@ -6895,13 +7675,13 @@ HTMLEditor::AutoDeleteRangesHandler::ExtendOrShrinkRangeToDelete(
// Don't cross flex-item/grid-item boundary to make new content inserted
// into it.
if (StaticPrefs::editor_block_inline_check_use_computed_style() &&
- forwardScanFromEndResult.ContentIsElement() &&
HTMLEditUtils::IsFlexOrGridItem(
*forwardScanFromEndResult.ElementPtr())) {
break;
}
rangeToDelete.SetEnd(
- forwardScanFromEndResult.PointAfterContent<EditorRawDOMPoint>());
+ forwardScanFromEndResult
+ .PointAfterReachedContent<EditorRawDOMPoint>());
continue;
}