commit 689e5a7f77a435da2762d39415b64ae31a78306b
parent c7c08652632ab2b3f56fda1dbb99f6f644103e8d
Author: Masayuki Nakano <masayuki@d-toybox.com>
Date: Fri, 3 Oct 2025 00:57:53 +0000
Bug 1990586 - Make `WhiteSpaceVisibilityKeeper::DeleteContentNodeAndJoinTextNodesAroundIt` track given caret point r=m_kato
It's used as the result in some cases. However, it's not tracked in
some cases when touching the DOM.
This modifies the API of `AutoTrackDOMPoint` to flush the tracking point
but can keep tracking. We should use the new API in every place later.
Differential Revision: https://phabricator.services.mozilla.com/D266752
Diffstat:
4 files changed, 47 insertions(+), 14 deletions(-)
diff --git a/editor/libeditor/EditorForwards.h b/editor/libeditor/EditorForwards.h
@@ -46,6 +46,7 @@ enum class EditorCommandParamType : uint16_t; // mozilla/EditorCommands.h
enum class EditSubAction : int32_t; // mozilla/EditAction.h
enum class ParagraphSeparator; // mozilla/HTMLEditor.h
enum class SpecifiedStyle : uint8_t; // mozilla/PendingStyles.h
+enum class StopTracking : bool; // mozilla/SelectionState.h
enum class SuggestCaret; // EditorUtils.h
enum class WithTransaction; // HTMLEditHelpers.h
diff --git a/editor/libeditor/SelectionState.h b/editor/libeditor/SelectionState.h
@@ -322,6 +322,8 @@ class MOZ_STACK_CLASS RangeUpdater final {
bool mLocked;
};
+enum class StopTracking : bool { No, Yes };
+
/**
* Helper class for using SelectionState. Stack based class for doing
* preservation of dom points across editor actions.
@@ -369,11 +371,13 @@ class MOZ_STACK_CLASS AutoTrackDOMPoint final {
~AutoTrackDOMPoint() { FlushAndStopTracking(); }
- void FlushAndStopTracking() {
+ void Flush(StopTracking aStopTracking) {
if (!mIsTracking) {
return;
}
- mIsTracking = false;
+ if (static_cast<bool>(aStopTracking)) {
+ mIsTracking = false;
+ }
if (mPoint.isSome()) {
mRangeUpdater.DropRangeItem(mRangeItem);
// Setting `mPoint` with invalid DOM point causes hitting `NS_ASSERTION()`
@@ -416,6 +420,8 @@ class MOZ_STACK_CLASS AutoTrackDOMPoint final {
}
}
+ void FlushAndStopTracking() { Flush(StopTracking::Yes); }
+
void StopTracking() { mIsTracking = false; }
private:
diff --git a/editor/libeditor/WhiteSpaceVisibilityKeeper.cpp b/editor/libeditor/WhiteSpaceVisibilityKeeper.cpp
@@ -2347,6 +2347,11 @@ WhiteSpaceVisibilityKeeper::DeleteContentNodeAndJoinTextNodesAroundIt(
return Err(NS_ERROR_FAILURE);
}
EditorDOMPoint pointToPutCaret(aCaretPoint);
+ Maybe<AutoTrackDOMPoint> trackPointToPutCaret;
+ if (aCaretPoint.IsSet()) {
+ trackPointToPutCaret.emplace(aHTMLEditor.RangeUpdaterRef(),
+ &pointToPutCaret);
+ }
// If we're removing a block, it may be surrounded by invisible
// white-spaces. We should remove them to avoid to make them accidentally
// visible.
@@ -2354,8 +2359,6 @@ WhiteSpaceVisibilityKeeper::DeleteContentNodeAndJoinTextNodesAroundIt(
aContentToDelete, BlockInlineCheck::UseComputedDisplayOutsideStyle)) {
AutoTrackDOMPoint trackAtContent(aHTMLEditor.RangeUpdaterRef(), &atContent);
{
- AutoTrackDOMPoint trackPointToPutCaret(aHTMLEditor.RangeUpdaterRef(),
- &pointToPutCaret);
nsresult rv =
WhiteSpaceVisibilityKeeper::EnsureNoInvisibleWhiteSpacesBefore(
aHTMLEditor, EditorDOMPoint(aContentToDelete.AsElement()));
@@ -2379,6 +2382,9 @@ WhiteSpaceVisibilityKeeper::DeleteContentNodeAndJoinTextNodesAroundIt(
if (NS_WARN_IF(!aContentToDelete.IsInComposedDoc())) {
return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
}
+ if (trackPointToPutCaret.isSome()) {
+ trackPointToPutCaret->Flush(StopTracking::No);
+ }
}
if (pointToPutCaret.IsInContentNode()) {
// Additionally, we may put caret into the preceding block (this is the
@@ -2452,7 +2458,7 @@ WhiteSpaceVisibilityKeeper::DeleteContentNodeAndJoinTextNodesAroundIt(
}
}
}
- trackAtContent.FlushAndStopTracking();
+ trackAtContent.Flush(StopTracking::Yes);
if (NS_WARN_IF(!atContent.IsInContentNodeAndValidInComposedDoc())) {
return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
}
@@ -2478,7 +2484,7 @@ WhiteSpaceVisibilityKeeper::DeleteContentNodeAndJoinTextNodesAroundIt(
"failed");
return afterLastVisibleThingOrError.propagateErr();
}
- trackAtContent.FlushAndStopTracking();
+ trackAtContent.Flush(StopTracking::Yes);
if (NS_WARN_IF(!atContent.IsInContentNodeAndValidInComposedDoc())) {
return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
}
@@ -2497,7 +2503,7 @@ WhiteSpaceVisibilityKeeper::DeleteContentNodeAndJoinTextNodesAroundIt(
"WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesBefore() failed");
return atFirstVisibleThingOrError.propagateErr();
}
- trackAtContent.FlushAndStopTracking();
+ trackAtContent.Flush(StopTracking::Yes);
if (NS_WARN_IF(!atContent.IsInContentNodeAndValidInComposedDoc())) {
return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
}
@@ -2507,13 +2513,16 @@ WhiteSpaceVisibilityKeeper::DeleteContentNodeAndJoinTextNodesAroundIt(
HTMLEditUtils::GetPreviousSibling(
aContentToDelete, {WalkTreeOption::IgnoreNonEditableNode});
// Delete the node, and join like nodes if appropriate
- {
- AutoTrackDOMPoint trackPointToPutCaret(aHTMLEditor.RangeUpdaterRef(),
- &pointToPutCaret);
- nsresult rv = aHTMLEditor.DeleteNodeWithTransaction(aContentToDelete);
- if (NS_FAILED(rv)) {
- NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
- return Err(rv);
+ nsresult rv = aHTMLEditor.DeleteNodeWithTransaction(aContentToDelete);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
+ return Err(rv);
+ }
+
+ if (trackPointToPutCaret.isSome()) {
+ trackPointToPutCaret->Flush(StopTracking::Yes);
+ if (NS_WARN_IF(!pointToPutCaret.IsInContentNode())) {
+ return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
}
}
diff --git a/testing/web-platform/tests/editing/crashtests/delete-in-empty-editable-document-element.html b/testing/web-platform/tests/editing/crashtests/delete-in-empty-editable-document-element.html
@@ -0,0 +1,17 @@
+<!doctype html>
+<html>
+<head>
+<meta charset="utf-8">
+<script>
+"use strict";
+
+addEventListener("DOMContentLoaded", () => {
+ document.documentElement.contentEditable = "plaintext-only";
+ document.execCommand("delete");
+}, {once: true});
+</script>
+</head>
+<body>
+<track></track>
+</body>
+</html>