Bug 1951518 - Make HTMLEditor::DeleteRangesWithTransaction stop deleting Text which has only a collapsible white-space r=m_kato

In bug 1925635, I moved the post processing after deleting ranges in
`EditorBase::DeleteRangesWithTransaction` into the `HTMLEditor`'s override.
At this time, I makes it uses `HTMLEditUtils::IsEmptyNode` to check whether
the `Text` and its containers become empty. Therefore, if `Ctrl`+`Backspace`
deletes the only word in the `Text` which starts with a collapsible white-space
causes deleting the `Text` after deleting all visible characters in the `Text`.

Therefore, this makes check it with `Text::TextDataLength` too before searching
the most distant ancestor inline element which becomes empty.

However, it may cause leading invisible white-spaces of the only word visible
because `nsFrameSelection` computes the word range strictly in the `Text`.
Therefore, this patch also makes `HTMLEditor::DeleteRangesWithTransaction` and
`AutoDeleteRangesHandler::ComputeRangesToDeleteRangesWithTransaction` extend
each range to delete to include surrounding invisible white-spaces too.

Therefore, this patch adds the new method to `AutoClonedRangeArray`.

Then, I hit 4 existing bugs with new test failures.

One is `HTMLEditUtils::LineRequiresPaddingLineBreakToBeVisible`.  It checks
whether the candidate point to insert a `<br>` is followed by a block boundary
first.  Then, it checks whether the candidate point follows a collapsible
white-space or a block boundary.  However, it uses
`WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary()` which ignores invisible
white-spaces.  Therefore, it will ignore the new invisible white-space and
reaches preceding `Text`.  Thus, it fails to put a `<br>` and makes the new
invisible white-space "fixed" as invisible.

Therefore, this patch rewrites the check with using
`HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement()`.

Next one is, `HTMLEditor::DeleteEmptyInclusiveAncestorInlineElements()`
returns end of deleted node if it's followed by a non-editable node, i.e.,
an element has `contenteditable="false"`.  Therefore, its caller inserts a
`<br>` to the end of the container when deleting preceding editable node of a
non-editable node.

Therefore, this patch removes the editable state check.

Next, `AutoBlockElementsJoiner::HandleDeleteNonCollapsedRange` may put a padding
`<br>` after the moved line, but it does not assume that the moved line does not
ends with a block boundary.  This causes failing #46 and #48 tests in
`text_bug772796.html`.  E.g., when pressing `Delete` in
`<div>foo[]<div><span style="white-space:pre"><div>bar</div>baz</span>`, we move
the second `<div>` as a child of the parent `<span>` to end of the first `<div>`
like `<div>foo<span style="white-space:pre"><div>bar</div></span></div>...`.
Without the change, it starts to put unnecessary `<br>` after ` the `<span>`
because of the bug fix in `HTMLEditUtils::LineRequiresPaddingLineBreakToBeVisible`
above.  This result is completely odd from the users point of view (looks like
just move caret), but we should avoid to put the unnecessary `<br>`.

Finally, we'll fail an assertion when putting caret at the last of
`AutoBlockElementsJoiner::DeleteContentInRange` because it forgets to flush
the tracking range before using it. This appeared by the changes above.

Therefore, this patch fixes this bug too.

Differential Revision: https://phabricator.services.mozilla.com/D240703
This commit is contained in:
Masayuki Nakano
2025-03-17 23:02:10 +00:00
parent b39e4558a2
commit 8e23c15e1b
13 changed files with 577 additions and 133 deletions

View File

@@ -4158,18 +4158,20 @@ HTMLEditor::DeleteEmptyInclusiveAncestorInlineElements(
const nsCOMPtr<nsIContent> nextSibling = content->GetNextSibling();
const nsCOMPtr<nsINode> parentNode = content->GetParentNode();
MOZ_ASSERT(parentNode);
nsresult rv = DeleteNodeWithTransaction(content);
if (NS_FAILED(rv)) {
NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
return Err(rv);
}
if (NS_WARN_IF(nextSibling && nextSibling->GetParentNode() != parentNode)) {
if (NS_WARN_IF(nextSibling && nextSibling->GetParentNode() != parentNode) ||
NS_WARN_IF(!HTMLEditUtils::IsSimplyEditableNode(*parentNode))) {
return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
}
return CaretPoint(nextSibling &&
HTMLEditUtils::IsSimplyEditableNode(*nextSibling)
? EditorDOMPoint(nextSibling)
: EditorDOMPoint::AtEndOf(*parentNode));
// Note that even if nextSibling is not editable, we can put caret before it
// unless parentNode is not editable.
return CaretPoint(nextSibling ? EditorDOMPoint(nextSibling)
: EditorDOMPoint::AtEndOf(*parentNode));
}
nsresult HTMLEditor::DeleteAllChildrenWithTransaction(Element& aElement) {