commit 05c50f4921e367bd4b0f673b7c38cbad20542ecf
parent 566d1009e7ce14808adf985ac141fea8d7918348
Author: Masayuki Nakano <masayuki@d-toybox.com>
Date: Fri, 26 Dec 2025 04:39:47 +0000
Bug 2007618 - Make `AutoEmptyBlockAncestorDeleter::GetNewCaretPosition` ignore invisible `Text` nodes r=m_kato
The remaining case of bug 2005895 is, there is a invisible `Text`
between paragraphs like:
```html
<p>abc</p> <p>{}<br></p>
```
The reason is, `HTMLEditUtils::GetPreviousContent` is not called with
`WalkTreeOption::IgnoreWhiteSpaceOnlyText`. Therefore, I tried to
add the option in `AutoEmptyBlockAncestorDeleter::GetNewCaretPosition`.
However, the handling is odd [1]:
```cpp
static bool IsContentIgnored(const nsIContent& aContent,
const WalkTreeOptions& aOptions) {
```
<snip>
```cpp
if (aOptions.contains(WalkTreeOption::IgnoreWhiteSpaceOnlyText) &&
aContent.IsText() &&
const_cast<Text*>(aContent.AsText())->TextIsOnlyWhitespace()) {
return true;
}
```
So, whether the `Text` is actually visible or not is not tested.
Therefore, this does not work with preformatted content. However, I'd
like to uplift this to ESR140 so that it's too risky to change the
extant `HTMLEditUtils` implementation. Therefore, this patch makes
`GetNewCaretPosition` ignore invisible `Text` nodes directly.
1. https://searchfox.org/firefox-main/rev/20a1fb35a4d5c2f2ea6c865ecebc8e4bee6f86c9/editor/libeditor/HTMLEditUtils.h#3268-3269,3279-3282
Differential Revision: https://phabricator.services.mozilla.com/D277498
Diffstat:
4 files changed, 78 insertions(+), 10 deletions(-)
diff --git a/editor/libeditor/HTMLEditorDeleteHandler.cpp b/editor/libeditor/HTMLEditorDeleteHandler.cpp
@@ -7203,11 +7203,23 @@ Result<CaretPoint, nsresult> HTMLEditor::AutoDeleteRangesHandler::
case nsIEditor::eToEndOfLine: {
// Collapse Selection to next node of after empty block element
// if there is. Otherwise, to just after the empty block.
- auto afterEmptyBlock(
- EditorDOMPoint::After(mEmptyInclusiveAncestorBlockElement));
- MOZ_ASSERT(afterEmptyBlock.IsSet());
- if (nsIContent* nextContentOfEmptyBlock = HTMLEditUtils::GetNextContent(
- afterEmptyBlock, {}, BlockInlineCheck::Unused, &aEditingHost)) {
+ nsIContent* const nextContentOfEmptyBlock = [&]() -> nsIContent* {
+ for (EditorRawDOMPoint scanStartPoint =
+ EditorRawDOMPoint::After(mEmptyInclusiveAncestorBlockElement);
+ scanStartPoint.IsInContentNode();) {
+ nsIContent* const nextContent = HTMLEditUtils::GetNextContent(
+ scanStartPoint, {}, BlockInlineCheck::Unused, &aEditingHost);
+ // Let's ignore invisible `Text`.
+ if (nextContent && nextContent->IsText() &&
+ !HTMLEditUtils::IsVisibleTextNode(*nextContent->AsText())) {
+ scanStartPoint = EditorRawDOMPoint::After(*nextContent);
+ continue;
+ }
+ return nextContent;
+ }
+ return nullptr;
+ }();
+ if (nextContentOfEmptyBlock) {
EditorDOMPoint pt = HTMLEditUtils::GetGoodCaretPointFor<EditorDOMPoint>(
*nextContentOfEmptyBlock, aDirectionAndAmount);
if (!pt.IsSet()) {
@@ -7216,6 +7228,8 @@ Result<CaretPoint, nsresult> HTMLEditor::AutoDeleteRangesHandler::
}
return CaretPoint(std::move(pt));
}
+ EditorDOMPoint afterEmptyBlock =
+ EditorDOMPoint::After(mEmptyInclusiveAncestorBlockElement);
if (NS_WARN_IF(!afterEmptyBlock.IsSet())) {
return Err(NS_ERROR_FAILURE);
}
@@ -7226,11 +7240,24 @@ Result<CaretPoint, nsresult> HTMLEditor::AutoDeleteRangesHandler::
case nsIEditor::eToBeginningOfLine: {
// Collapse Selection to previous editable node of the empty block
// if there is.
- EditorRawDOMPoint atEmptyBlock(mEmptyInclusiveAncestorBlockElement);
- if (nsIContent* const previousContentOfEmptyBlock =
- HTMLEditUtils::GetPreviousContent(
- atEmptyBlock, {WalkTreeOption::IgnoreNonEditableNode},
- BlockInlineCheck::Unused, &aEditingHost)) {
+ nsIContent* const previousContentOfEmptyBlock = [&]() -> nsIContent* {
+ for (EditorRawDOMPoint scanStartPoint =
+ EditorRawDOMPoint(mEmptyInclusiveAncestorBlockElement);
+ scanStartPoint.IsInContentNode();) {
+ nsIContent* const previousContent = HTMLEditUtils::GetPreviousContent(
+ scanStartPoint, {WalkTreeOption::IgnoreNonEditableNode},
+ BlockInlineCheck::Unused, &aEditingHost);
+ // Let's ignore invisible `Text`.
+ if (previousContent && previousContent->IsText() &&
+ !HTMLEditUtils::IsVisibleTextNode(*previousContent->AsText())) {
+ scanStartPoint = EditorRawDOMPoint(previousContent, 0u);
+ continue;
+ }
+ return previousContent;
+ }
+ return nullptr;
+ }();
+ if (previousContentOfEmptyBlock) {
const EditorRawDOMPoint atEndOfPreviousContent =
HTMLEditUtils::GetGoodCaretPointFor<EditorRawDOMPoint>(
*previousContentOfEmptyBlock, aDirectionAndAmount);
diff --git a/testing/web-platform/tests/editing/data/delete.js b/testing/web-platform/tests/editing/data/delete.js
@@ -3506,6 +3506,16 @@ var browserTests = [
"<p>abc</p>",
[true],
{}],
+["<p>abc<br> </p> <p>{}<br></p>",
+ [["delete",""]],
+ "<p>abc</p>",
+ [true],
+ {}],
+["<div style=white-space:pre><p>abc</p> <p>{}<br></p></div>",
+ [["delete",""]],
+ "<div style=\"white-space:pre\"><p>abc</p> </div>",
+ [true],
+ {}],
// The following tests are ported by Mozilla from their old test and the
// expectations are based on Chrome's behavior unless the behavior does not
diff --git a/testing/web-platform/tests/editing/data/forwarddelete.js b/testing/web-platform/tests/editing/data/forwarddelete.js
@@ -3368,4 +3368,14 @@ var browserTests = [
"<p>abc</p>",
[true],
{}],
+["<p>abc{}<br> </p> <p><br></p>",
+ [["forwarddelete",""]],
+ "<p>abc</p>",
+ [true],
+ {}],
+["<div style=white-space:pre><p>abc{}</p> <p><br></p></div>",
+ [["forwarddelete",""]],
+ "<div style=\"white-space:pre\"><p>abc </p><p><br></p></div>",
+ [true],
+ {}],
]
diff --git a/testing/web-platform/tests/editing/data/multitest.js b/testing/web-platform/tests/editing/data/multitest.js
@@ -3260,4 +3260,25 @@ var browserTests = [
["<p><b>abc </b>d</p>", "<p><b>abc </b>d</p>"],
[true,true,true],
{}],
+
+["<p>abc<br> </p> <p>{}<br></p>",
+ [["delete",""],["inserttext","d"]],
+ "<p>abcd</p>",
+ [true,true],
+ {}],
+["<div style=white-space:pre><p>abc</p> <p>{}<br></p></div>",
+ [["delete",""],["inserttext","d"]],
+ "<div style=\"white-space:pre\"><p>abc</p> d</div>",
+ [true,true],
+ {}],
+["<p>abc[]<br> </p> <p><br></p>",
+ [["forwarddelete",""],["inserttext","d"]],
+ "<p>abcd</p>",
+ [true,true],
+ {}],
+["<div style=white-space:pre><p>abc[]</p> <p><br></p></div>",
+ [["forwarddelete",""],["inserttext","d"]],
+ "<div style=\"white-space:pre\"><p>abcd </p><p><br></p></div>",
+ [true,true],
+ {}],
]