Bug 1990586 - Make WhiteSpaceVisibilityKeeper::DeleteContentNodeAndJoinTextNodesAroundIt track given caret point a=pascalc

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.

(This manually merges `WhiteSpaceVisibilityKeeper.cpp` because ESR140
still has the pref to take the legacy mode back and that blocks applying
the patch cleanly.)

Differential Revision: https://phabricator.services.mozilla.com/D270643
This commit is contained in:
Masayuki Nakano
2025-10-30 08:51:11 +00:00
committed by pchevrel@mozilla.com
parent 3c22b4c56f
commit 0c6404cf72
4 changed files with 48 additions and 9 deletions

View File

@@ -46,6 +46,7 @@ enum class EditorCommandParamType : uint16_t; // mozilla/EditorCommands.h
enum class EditSubAction : int32_t; // mozilla/EditAction.h enum class EditSubAction : int32_t; // mozilla/EditAction.h
enum class ParagraphSeparator; // mozilla/HTMLEditor.h enum class ParagraphSeparator; // mozilla/HTMLEditor.h
enum class SpecifiedStyle : uint8_t; // mozilla/PendingStyles.h enum class SpecifiedStyle : uint8_t; // mozilla/PendingStyles.h
enum class StopTracking : bool; // mozilla/SelectionState.h
enum class SuggestCaret; // EditorUtils.h enum class SuggestCaret; // EditorUtils.h
enum class WithTransaction; // HTMLEditHelpers.h enum class WithTransaction; // HTMLEditHelpers.h

View File

@@ -284,6 +284,8 @@ class MOZ_STACK_CLASS RangeUpdater final {
bool mLocked; bool mLocked;
}; };
enum class StopTracking : bool { No, Yes };
/** /**
* Helper class for using SelectionState. Stack based class for doing * Helper class for using SelectionState. Stack based class for doing
* preservation of dom points across editor actions. * preservation of dom points across editor actions.
@@ -331,11 +333,13 @@ class MOZ_STACK_CLASS AutoTrackDOMPoint final {
~AutoTrackDOMPoint() { FlushAndStopTracking(); } ~AutoTrackDOMPoint() { FlushAndStopTracking(); }
void FlushAndStopTracking() { void Flush(StopTracking aStopTracking) {
if (!mIsTracking) { if (!mIsTracking) {
return; return;
} }
mIsTracking = false; if (static_cast<bool>(aStopTracking)) {
mIsTracking = false;
}
if (mPoint.isSome()) { if (mPoint.isSome()) {
mRangeUpdater.DropRangeItem(mRangeItem); mRangeUpdater.DropRangeItem(mRangeItem);
// Setting `mPoint` with invalid DOM point causes hitting `NS_ASSERTION()` // Setting `mPoint` with invalid DOM point causes hitting `NS_ASSERTION()`
@@ -378,6 +382,8 @@ class MOZ_STACK_CLASS AutoTrackDOMPoint final {
} }
} }
void FlushAndStopTracking() { Flush(StopTracking::Yes); }
void StopTracking() { mIsTracking = false; } void StopTracking() { mIsTracking = false; }
private: private:

View File

@@ -3450,6 +3450,11 @@ WhiteSpaceVisibilityKeeper::DeleteContentNodeAndJoinTextNodesAroundIt(
MOZ_ASSERT( MOZ_ASSERT(
StaticPrefs::editor_white_space_normalization_blink_compatible()); StaticPrefs::editor_white_space_normalization_blink_compatible());
Maybe<AutoTrackDOMPoint> trackPointToPutCaret;
if (aCaretPoint.IsSet()) {
trackPointToPutCaret.emplace(aHTMLEditor.RangeUpdaterRef(),
&pointToPutCaret);
}
// If we're removing a block, it may be surrounded by invisible // If we're removing a block, it may be surrounded by invisible
// white-spaces. We should remove them to avoid to make them accidentally // white-spaces. We should remove them to avoid to make them accidentally
// visible. // visible.
@@ -3459,8 +3464,6 @@ WhiteSpaceVisibilityKeeper::DeleteContentNodeAndJoinTextNodesAroundIt(
AutoTrackDOMPoint trackAtContent(aHTMLEditor.RangeUpdaterRef(), AutoTrackDOMPoint trackAtContent(aHTMLEditor.RangeUpdaterRef(),
&atContent); &atContent);
{ {
AutoTrackDOMPoint trackPointToPutCaret(aHTMLEditor.RangeUpdaterRef(),
&pointToPutCaret);
nsresult rv = nsresult rv =
WhiteSpaceVisibilityKeeper::EnsureNoInvisibleWhiteSpacesBefore( WhiteSpaceVisibilityKeeper::EnsureNoInvisibleWhiteSpacesBefore(
aHTMLEditor, EditorDOMPoint(aContentToDelete.AsElement())); aHTMLEditor, EditorDOMPoint(aContentToDelete.AsElement()));
@@ -3484,6 +3487,9 @@ WhiteSpaceVisibilityKeeper::DeleteContentNodeAndJoinTextNodesAroundIt(
if (NS_WARN_IF(!aContentToDelete.IsInComposedDoc())) { if (NS_WARN_IF(!aContentToDelete.IsInComposedDoc())) {
return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
} }
if (trackPointToPutCaret.isSome()) {
trackPointToPutCaret->Flush(StopTracking::No);
}
} }
if (pointToPutCaret.IsInContentNode()) { if (pointToPutCaret.IsInContentNode()) {
// Additionally, we may put caret into the preceding block (this is the // Additionally, we may put caret into the preceding block (this is the
@@ -3558,7 +3564,7 @@ WhiteSpaceVisibilityKeeper::DeleteContentNodeAndJoinTextNodesAroundIt(
} }
} }
} }
trackAtContent.FlushAndStopTracking(); trackAtContent.Flush(StopTracking::Yes);
if (NS_WARN_IF(!atContent.IsInContentNodeAndValidInComposedDoc())) { if (NS_WARN_IF(!atContent.IsInContentNodeAndValidInComposedDoc())) {
return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
} }
@@ -3584,7 +3590,7 @@ WhiteSpaceVisibilityKeeper::DeleteContentNodeAndJoinTextNodesAroundIt(
"failed"); "failed");
return afterLastVisibleThingOrError.propagateErr(); return afterLastVisibleThingOrError.propagateErr();
} }
trackAtContent.FlushAndStopTracking(); trackAtContent.Flush(StopTracking::Yes);
if (NS_WARN_IF(!atContent.IsInContentNodeAndValidInComposedDoc())) { if (NS_WARN_IF(!atContent.IsInContentNodeAndValidInComposedDoc())) {
return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
} }
@@ -3604,7 +3610,7 @@ WhiteSpaceVisibilityKeeper::DeleteContentNodeAndJoinTextNodesAroundIt(
"WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesBefore() failed"); "WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesBefore() failed");
return atFirstVisibleThingOrError.propagateErr(); return atFirstVisibleThingOrError.propagateErr();
} }
trackAtContent.FlushAndStopTracking(); trackAtContent.Flush(StopTracking::Yes);
if (NS_WARN_IF(!atContent.IsInContentNodeAndValidInComposedDoc())) { if (NS_WARN_IF(!atContent.IsInContentNodeAndValidInComposedDoc())) {
return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
} }
@@ -3616,13 +3622,22 @@ WhiteSpaceVisibilityKeeper::DeleteContentNodeAndJoinTextNodesAroundIt(
aContentToDelete, {WalkTreeOption::IgnoreNonEditableNode}); aContentToDelete, {WalkTreeOption::IgnoreNonEditableNode});
// Delete the node, and join like nodes if appropriate // Delete the node, and join like nodes if appropriate
{ {
AutoTrackDOMPoint trackPointToPutCaret(aHTMLEditor.RangeUpdaterRef(), Maybe<AutoTrackDOMPoint> trackPointToPutCaret;
&pointToPutCaret); if (pointToPutCaret.IsSet()) {
trackPointToPutCaret.emplace(aHTMLEditor.RangeUpdaterRef(),
&pointToPutCaret);
}
nsresult rv = aHTMLEditor.DeleteNodeWithTransaction(aContentToDelete); nsresult rv = aHTMLEditor.DeleteNodeWithTransaction(aContentToDelete);
if (NS_FAILED(rv)) { if (NS_FAILED(rv)) {
NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed"); NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
return Err(rv); return Err(rv);
} }
if (trackPointToPutCaret.isSome()) {
trackPointToPutCaret->Flush(StopTracking::Yes);
if (NS_WARN_IF(!pointToPutCaret.IsInContentNode())) {
return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
}
}
} }
// Are they both text nodes? If so, join them! // Are they both text nodes? If so, join them!

View File

@@ -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>