Bug 1815383 - part 3: Make ReplaceTextTransaction::DoTransaction stop updating Selection directly r=m_kato

And also this changes `HTMLEditor::ReplaceTextWithTransaction` which is the only
user of `ReplaceTextTransaction`.

Depends on D169745

Differential Revision: https://phabricator.services.mozilla.com/D169746
This commit is contained in:
Masayuki Nakano 2023-02-17 09:42:31 +00:00
parent 7fe7780038
commit 6daaabe189
9 changed files with 190 additions and 199 deletions

View File

@ -269,11 +269,17 @@ class MOZ_STACK_CLASS InsertTextResult final : public CaretPoint {
explicit InsertTextResult(EditorDOMPointInText&& aEndOfInsertedText)
: CaretPoint(EditorDOMPoint()),
mEndOfInsertedText(std::move(aEndOfInsertedText)) {}
template <typename EditorDOMPointType>
template <typename PT, typename CT>
InsertTextResult(EditorDOMPointInText&& aEndOfInsertedText,
const EditorDOMPointType& aCaretPoint)
const EditorDOMPointBase<PT, CT>& aCaretPoint)
: CaretPoint(aCaretPoint.template To<EditorDOMPoint>()),
mEndOfInsertedText(std::move(aEndOfInsertedText)) {}
InsertTextResult(EditorDOMPointInText&& aEndOfInsertedText,
CaretPoint&& aCaretPoint)
: CaretPoint(std::move(aCaretPoint)),
mEndOfInsertedText(std::move(aEndOfInsertedText)) {
UnmarkAsHandledCaretPoint();
}
[[nodiscard]] bool Handled() const { return mEndOfInsertedText.IsSet(); }
const EditorDOMPointInText& EndOfInsertedTextRef() const {

View File

@ -519,6 +519,13 @@ nsresult HTMLEditor::OnEndHandlingTopLevelEditSubActionInternal() {
case EditSubAction::eInsertParagraphSeparator:
case EditSubAction::ePasteHTMLContent:
case EditSubAction::eInsertHTMLSource: {
// Due to the replacement of white-spaces in
// WhiteSpaceVisibilityKeeper::NormalizeVisibleWhiteSpacesAt(),
// selection ranges may be changed since DOM ranges track the DOM
// mutation by themselves. However, we want to keep selection as-is.
// Therefore, we should restore `Selection` after replacing
// white-spaces.
AutoSelectionRestorer restoreSelection(*this);
// TODO: Temporarily, WhiteSpaceVisibilityKeeper replaces ASCII
// white-spaces with NPSPs and then, we'll replace them with ASCII
// white-spaces here. We should avoid this overwriting things as
@ -2906,7 +2913,7 @@ void HTMLEditor::ExtendRangeToDeleteWithNormalizingWhiteSpaces(
}
}
Result<EditorDOMPoint, nsresult>
Result<CaretPoint, nsresult>
HTMLEditor::DeleteTextAndNormalizeSurroundingWhiteSpaces(
const EditorDOMPointInText& aStartToDelete,
const EditorDOMPointInText& aEndToDelete,
@ -2932,7 +2939,7 @@ HTMLEditor::DeleteTextAndNormalizeSurroundingWhiteSpaces(
// normalize white-space sequence, but there is no white-spaces which need to
// be replaced, we need to do nothing here.
if (startToDelete == endToDelete) {
return aStartToDelete.To<EditorDOMPoint>();
return CaretPoint(aStartToDelete.To<EditorDOMPoint>());
}
// Note that the container text node of startToDelete may be removed from
@ -2964,14 +2971,17 @@ HTMLEditor::DeleteTextAndNormalizeSurroundingWhiteSpaces(
? trackingEndToDelete.Offset() - startToDelete.Offset()
: startToDelete.ContainerAs<Text>()->TextLength() -
startToDelete.Offset();
nsresult rv = ReplaceTextWithTransaction(
MOZ_KnownLive(*startToDelete.ContainerAs<Text>()),
startToDelete.Offset(), lengthToReplaceInFirstTextNode,
normalizedWhiteSpacesInFirstNode);
if (NS_FAILED(rv)) {
Result<InsertTextResult, nsresult> replaceTextResult =
ReplaceTextWithTransaction(
MOZ_KnownLive(*startToDelete.ContainerAs<Text>()),
startToDelete.Offset(), lengthToReplaceInFirstTextNode,
normalizedWhiteSpacesInFirstNode);
if (MOZ_UNLIKELY(replaceTextResult.isErr())) {
NS_WARNING("HTMLEditor::ReplaceTextWithTransaction() failed");
return Err(rv);
return replaceTextResult.propagateErr();
}
// We'll return computed caret point, newCaretPosition, below.
replaceTextResult.unwrap().IgnoreCaretPointSuggestion();
if (startToDelete.ContainerAs<Text>() ==
trackingEndToDelete.ContainerAs<Text>()) {
MOZ_ASSERT(normalizedWhiteSpacesInLastNode.IsEmpty());
@ -3038,22 +3048,23 @@ HTMLEditor::DeleteTextAndNormalizeSurroundingWhiteSpaces(
MOZ_ASSERT(!normalizedWhiteSpacesInLastNode.IsEmpty());
MOZ_ASSERT(startToDelete.ContainerAs<Text>() ==
endToDelete.ContainerAs<Text>());
nsresult rv = ReplaceTextWithTransaction(
MOZ_KnownLive(*startToDelete.ContainerAs<Text>()),
startToDelete.Offset(), endToDelete.Offset() - startToDelete.Offset(),
normalizedWhiteSpacesInLastNode);
if (NS_FAILED(rv)) {
Result<InsertTextResult, nsresult> replaceTextResult =
ReplaceTextWithTransaction(
MOZ_KnownLive(*startToDelete.ContainerAs<Text>()),
startToDelete.Offset(),
endToDelete.Offset() - startToDelete.Offset(),
normalizedWhiteSpacesInLastNode);
if (MOZ_UNLIKELY(replaceTextResult.isErr())) {
NS_WARNING("HTMLEditor::ReplaceTextWithTransaction() failed");
return Err(rv);
return replaceTextResult.propagateErr();
}
// We'll return computed caret point, newCaretPosition, below.
replaceTextResult.unwrap().IgnoreCaretPointSuggestion();
break;
}
if (!newCaretPosition.IsSetAndValid() ||
!newCaretPosition.GetContainer()->IsInComposedDoc()) {
NS_WARNING(
"HTMLEditor::DeleteTextAndNormalizeSurroundingWhiteSpaces() got lost "
"the modifying line");
if (NS_WARN_IF(!newCaretPosition.IsSetAndValid()) ||
NS_WARN_IF(!newCaretPosition.GetContainer()->IsInComposedDoc())) {
return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
}
@ -3121,7 +3132,7 @@ HTMLEditor::DeleteTextAndNormalizeSurroundingWhiteSpaces(
NS_WARNING("Inserting <br> element caused unexpected DOM tree");
return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
}
return newCaretPosition;
return CaretPoint(std::move(newCaretPosition));
}
nsresult HTMLEditor::InsertBRElementIfHardLineIsEmptyAndEndsWithBlockBoundary(

View File

@ -3830,7 +3830,7 @@ Result<CaretPoint, nsresult> HTMLEditor::DeleteTextWithTransaction(
return caretPointOrError;
}
nsresult HTMLEditor::ReplaceTextWithTransaction(
Result<InsertTextResult, nsresult> HTMLEditor::ReplaceTextWithTransaction(
Text& aTextNode, uint32_t aOffset, uint32_t aLength,
const nsAString& aStringToInsert) {
MOZ_ASSERT(IsEditActionDataAvailable());
@ -3843,48 +3843,25 @@ nsresult HTMLEditor::ReplaceTextWithTransaction(
NS_WARNING("HTMLEditor::DeleteTextWithTransaction() failed");
return caretPointOrError.propagateErr();
}
nsresult rv = caretPointOrError.inspect().SuggestCaretPointTo(
*this, {SuggestCaret::OnlyIfHasSuggestion,
SuggestCaret::OnlyIfTransactionsAllowedToDoIt,
SuggestCaret::AndIgnoreTrivialError});
if (NS_FAILED(rv)) {
NS_WARNING("CaretPoint::SuggestCaretPointTo() failed");
return Err(rv);
}
NS_WARNING_ASSERTION(
rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
"CaretPoint::SuggestCaretPointTo() failed, but ignored");
return NS_OK;
return InsertTextResult(EditorDOMPointInText(&aTextNode, aOffset),
caretPointOrError.unwrap());
}
if (!aLength) {
RefPtr<Document> document = GetDocument();
if (NS_WARN_IF(!document)) {
return NS_ERROR_NOT_INITIALIZED;
return Err(NS_ERROR_NOT_INITIALIZED);
}
Result<InsertTextResult, nsresult> insertTextResult =
InsertTextWithTransaction(*document, aStringToInsert,
EditorDOMPoint(&aTextNode, aOffset));
if (MOZ_UNLIKELY(insertTextResult.isErr())) {
NS_WARNING("HTMLEditor::InsertTextWithTransaction() failed");
return insertTextResult.unwrapErr();
}
nsresult rv = insertTextResult.unwrap().SuggestCaretPointTo(
*this, {SuggestCaret::OnlyIfHasSuggestion,
SuggestCaret::OnlyIfTransactionsAllowedToDoIt,
SuggestCaret::AndIgnoreTrivialError});
if (NS_FAILED(rv)) {
NS_WARNING("CaretPoint::SuggestCaretPointTo() failed");
return Err(rv);
}
NS_WARNING_ASSERTION(
rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
"CaretPoint::SuggestCaretPointTo() failed, but ignored");
return NS_OK;
NS_WARNING_ASSERTION(insertTextResult.isOk(),
"HTMLEditor::InsertTextWithTransaction() failed");
return insertTextResult;
}
if (NS_WARN_IF(!HTMLEditUtils::IsSimplyEditableNode(aTextNode))) {
return NS_ERROR_FAILURE;
return Err(NS_ERROR_FAILURE);
}
// This should emulates inserting text for better undo/redo behavior.
@ -3892,7 +3869,7 @@ nsresult HTMLEditor::ReplaceTextWithTransaction(
AutoEditSubActionNotifier startToHandleEditSubAction(
*this, EditSubAction::eInsertText, nsIEditor::eNext, ignoredError);
if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
return EditorBase::ToGenericNSResult(ignoredError.StealNSResult());
return Err(NS_ERROR_EDITOR_DESTROYED);
}
NS_WARNING_ASSERTION(
!ignoredError.Failed(),
@ -3902,30 +3879,6 @@ nsresult HTMLEditor::ReplaceTextWithTransaction(
// the point may become invalid offset after that.
EditorDOMPointInText pointToInsert(&aTextNode, aOffset);
// `ReplaceTextTransaction()` removes the replaced text first, then,
// insert new text. Therefore, if selection is in the text node, the
// range is moved to start of the range and deletion and never adjusted
// for the inserting text since the change occurs after the range.
// Therefore, we might need to save/restore selection here.
Maybe<AutoSelectionRestorer> restoreSelection;
if (!AllowsTransactionsToChangeSelection() && !ArePreservingSelection()) {
const uint32_t rangeCount = SelectionRef().RangeCount();
for (const uint32_t i : IntegerRange(rangeCount)) {
MOZ_ASSERT(SelectionRef().RangeCount() == rangeCount);
const nsRange* range = SelectionRef().GetRangeAt(i);
if (MOZ_UNLIKELY(!range)) {
continue;
}
if ((range->GetStartContainer() == &aTextNode &&
range->StartOffset() >= aOffset) ||
(range->GetEndContainer() == &aTextNode &&
range->EndOffset() >= aOffset)) {
restoreSelection.emplace(*this);
break;
}
}
}
RefPtr<ReplaceTextTransaction> transaction = ReplaceTextTransaction::Create(
*this, aStringToInsert, aTextNode, aOffset, aLength);
MOZ_ASSERT(transaction);
@ -3944,6 +3897,12 @@ nsresult HTMLEditor::ReplaceTextWithTransaction(
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
"EditorBase::DoTransactionInternal() failed");
// Don't check whether we've been destroyed here because we need to notify
// listeners and observers below even if we've already destroyed.
EditorDOMPointInText endOfInsertedText(&aTextNode,
aOffset + aStringToInsert.Length());
if (pointToInsert.IsSet()) {
auto [begin, end] = ComputeInsertedRange(pointToInsert, aStringToInsert);
if (begin.IsSet() && end.IsSet()) {
@ -3952,11 +3911,9 @@ nsresult HTMLEditor::ReplaceTextWithTransaction(
TopLevelEditSubActionDataRef().DidInsertText(
*this, begin.To<EditorRawDOMPoint>(), end.To<EditorRawDOMPoint>());
}
}
// Now, restores selection for allowing the following listeners to modify
// selection.
restoreSelection.reset();
// XXX Should we update endOfInsertedText here?
}
if (!mActionListeners.IsEmpty()) {
for (auto& listener : mActionListeners.Clone()) {
@ -3968,7 +3925,13 @@ nsresult HTMLEditor::ReplaceTextWithTransaction(
}
}
return NS_WARN_IF(Destroyed()) ? NS_ERROR_EDITOR_DESTROYED : rv;
if (NS_WARN_IF(Destroyed())) {
return Err(NS_ERROR_EDITOR_DESTROYED);
}
return InsertTextResult(
std::move(endOfInsertedText),
transaction->SuggestPointToPutCaret<EditorDOMPoint>());
}
Result<InsertTextResult, nsresult> HTMLEditor::InsertTextWithTransaction(

View File

@ -744,12 +744,16 @@ class HTMLEditor final : public EditorBase,
uint32_t aLength);
/**
* ReplaceTextWithTransaction() replaces text in the range with
* aStringToInsert.
* Replace text in the range with aStringToInsert. If there is a DOM range
* exactly same as the replacing range, it'll be collapsed to
* {aTextNode, aOffset} because of the order of deletion and insertion.
* Therefore, the callers may need to handle `Selection` even when callers
* do not want to update `Selection`.
*/
[[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult ReplaceTextWithTransaction(
dom::Text& aTextNode, uint32_t aOffset, uint32_t aLength,
const nsAString& aStringToInsert);
[[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<InsertTextResult, nsresult>
ReplaceTextWithTransaction(dom::Text& aTextNode, uint32_t aOffset,
uint32_t aLength,
const nsAString& aStringToInsert);
/**
* Insert aStringToInsert to aPointToInsert. If the point is not editable,
@ -2002,7 +2006,7 @@ class HTMLEditor final : public EditorBase,
Forward,
Backward,
};
[[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditorDOMPoint, nsresult>
[[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<CaretPoint, nsresult>
DeleteTextAndNormalizeSurroundingWhiteSpaces(
const EditorDOMPointInText& aStartToDelete,
const EditorDOMPointInText& aEndToDelete,

View File

@ -2083,8 +2083,7 @@ HTMLEditor::AutoDeleteRangesHandler::HandleDeleteTextAroundCollapsedRanges(
return Err(NS_ERROR_FAILURE);
}
AutoTransactionsConserveSelection dontChangeMySelection(aHTMLEditor);
Result<EditorDOMPoint, nsresult> result =
Result<CaretPoint, nsresult> caretPointOrError =
aHTMLEditor.DeleteTextAndNormalizeSurroundingWhiteSpaces(
rangeToDelete.StartRef().AsInText(),
rangeToDelete.EndRef().AsInText(),
@ -2092,20 +2091,20 @@ HTMLEditor::AutoDeleteRangesHandler::HandleDeleteTextAroundCollapsedRanges(
aDirectionAndAmount == nsIEditor::eNext ? DeleteDirection::Forward
: DeleteDirection::Backward);
aHTMLEditor.TopLevelEditSubActionDataRef().mDidNormalizeWhitespaces = true;
if (MOZ_UNLIKELY(result.isErr())) {
if (MOZ_UNLIKELY(caretPointOrError.isErr())) {
NS_WARNING(
"HTMLEditor::DeleteTextAndNormalizeSurroundingWhiteSpaces() failed");
return result.propagateErr();
return caretPointOrError.propagateErr();
}
const EditorDOMPoint& newCaretPosition = result.inspect();
MOZ_ASSERT(newCaretPosition.IsSetAndValid());
rv = aHTMLEditor.CollapseSelectionTo(newCaretPosition);
if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
return Err(NS_ERROR_EDITOR_DESTROYED);
rv = caretPointOrError.unwrap().SuggestCaretPointTo(
aHTMLEditor,
{SuggestCaret::OnlyIfHasSuggestion, SuggestCaret::AndIgnoreTrivialError});
if (NS_FAILED(rv)) {
NS_WARNING("CaretPoint::SuggestCaretPointTo() failed");
return Err(rv);
}
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
"EditorBase::CollapseSelectionTo() failed, but ignored");
NS_WARNING_ASSERTION(rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
"CaretPoint::SuggestCaretPointTo() failed, but ignored");
return EditActionResult::HandledResult();
}

View File

@ -63,22 +63,6 @@ NS_IMETHODIMP ReplaceTextTransaction::DoTransaction() {
editorBase->RangeUpdaterRef().SelAdjReplaceText(textNode, mOffset,
mStringToBeReplaced.Length(),
mStringToInsert.Length());
if (!editorBase->AllowsTransactionsToChangeSelection()) {
return NS_OK;
}
// XXX Should we stop setting selection when mutation event listener
// modifies the text node?
editorBase->CollapseSelectionTo(
EditorRawDOMPoint(textNode, mOffset + mStringToInsert.Length()), error);
if (MOZ_UNLIKELY(error.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
NS_WARNING(
"EditorBase::CollapseSelectionTo() caused destroying the editor");
return NS_ERROR_EDITOR_DESTROYED;
}
NS_ASSERTION(!error.Failed(),
"EditorBase::CollapseSelectionTo() failed, but ignored");
return NS_OK;
}
@ -187,8 +171,8 @@ NS_IMETHODIMP ReplaceTextTransaction::RedoTransaction() {
// XXX Should we stop setting selection when mutation event listener
// modifies the text node?
editorBase->CollapseSelectionTo(
EditorRawDOMPoint(textNode, mOffset + mStringToInsert.Length()), error);
editorBase->CollapseSelectionTo(SuggestPointToPutCaret<EditorRawDOMPoint>(),
error);
if (MOZ_UNLIKELY(error.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
NS_WARNING(
"EditorBase::CollapseSelectionTo() caused destroying the editor");

View File

@ -69,6 +69,14 @@ class ReplaceTextTransaction final : public EditTransactionBase {
MOZ_CAN_RUN_SCRIPT NS_IMETHOD RedoTransaction() final;
template <typename EditorDOMPointType>
EditorDOMPointType SuggestPointToPutCaret() const {
if (NS_WARN_IF(!mTextNode)) {
return EditorDOMPointType();
}
return EditorDOMPointType(mTextNode, mOffset + mStringToInsert.Length());
}
friend std::ostream& operator<<(std::ostream& aStream,
const ReplaceTextTransaction& aTransaction);

View File

@ -63,8 +63,6 @@ template EditorRawDOMPoint WSRunScanner::GetFirstVisiblePoint(
template nsresult WhiteSpaceVisibilityKeeper::NormalizeVisibleWhiteSpacesAt(
HTMLEditor& aHTMLEditor, const EditorDOMPoint& aScanStartPoint);
template nsresult WhiteSpaceVisibilityKeeper::NormalizeVisibleWhiteSpacesAt(
HTMLEditor& aHTMLEditor, const EditorRawDOMPoint& aScanStartPoint);
template nsresult WhiteSpaceVisibilityKeeper::NormalizeVisibleWhiteSpacesAt(
HTMLEditor& aHTMLEditor, const EditorDOMPointInText& aScanStartPoint);
@ -938,15 +936,18 @@ WhiteSpaceVisibilityKeeper::InsertBRElement(
atNBSPReplacedWithASCIIWhiteSpace.IsCharNBSP()) {
AutoTrackDOMPoint trackPointToInsert(aHTMLEditor.RangeUpdaterRef(),
&pointToInsert);
AutoTransactionsConserveSelection dontChangeMySelection(aHTMLEditor);
nsresult rv = aHTMLEditor.ReplaceTextWithTransaction(
MOZ_KnownLive(
*atNBSPReplacedWithASCIIWhiteSpace.ContainerAs<Text>()),
atNBSPReplacedWithASCIIWhiteSpace.Offset(), 1, u" "_ns);
if (NS_FAILED(rv)) {
Result<InsertTextResult, nsresult> replaceTextResult =
aHTMLEditor.ReplaceTextWithTransaction(
MOZ_KnownLive(
*atNBSPReplacedWithASCIIWhiteSpace.ContainerAs<Text>()),
atNBSPReplacedWithASCIIWhiteSpace.Offset(), 1, u" "_ns);
if (MOZ_UNLIKELY(replaceTextResult.isErr())) {
NS_WARNING("HTMLEditor::ReplaceTextWithTransaction() failed failed");
return Err(rv);
return replaceTextResult.propagateErr();
}
// Ignore caret suggestion because there was
// AutoTransactionsConserveSelection.
replaceTextResult.unwrap().IgnoreCaretPointSuggestion();
// Don't refer the following variables anymore unless tracking the
// change.
atNBSPReplaceableWithSP.Clear();
@ -1099,15 +1100,18 @@ Result<InsertTextResult, nsresult> WhiteSpaceVisibilityKeeper::ReplaceText(
AutoTrackDOMRange trackInvisibleTrailingWhiteSpaceRange(
aHTMLEditor.RangeUpdaterRef(),
&invisibleTrailingWhiteSpaceRangeAtEnd);
AutoTransactionsConserveSelection dontChangeMySelection(aHTMLEditor);
nsresult rv = aHTMLEditor.ReplaceTextWithTransaction(
MOZ_KnownLive(
*atNBSPReplacedWithASCIIWhiteSpace.ContainerAs<Text>()),
atNBSPReplacedWithASCIIWhiteSpace.Offset(), 1, u" "_ns);
if (MOZ_UNLIKELY(NS_FAILED(rv))) {
Result<InsertTextResult, nsresult> replaceTextResult =
aHTMLEditor.ReplaceTextWithTransaction(
MOZ_KnownLive(
*atNBSPReplacedWithASCIIWhiteSpace.ContainerAs<Text>()),
atNBSPReplacedWithASCIIWhiteSpace.Offset(), 1, u" "_ns);
if (MOZ_UNLIKELY(replaceTextResult.isErr())) {
NS_WARNING("HTMLEditor::ReplaceTextWithTransaction() failed");
return Err(rv);
return replaceTextResult.propagateErr();
}
// Ignore caret suggestion because there was
// AutoTransactionsConserveSelection.
replaceTextResult.unwrap().IgnoreCaretPointSuggestion();
}
}
@ -1152,15 +1156,18 @@ Result<InsertTextResult, nsresult> WhiteSpaceVisibilityKeeper::ReplaceText(
AutoTrackDOMRange trackInvisibleTrailingWhiteSpaceRange(
aHTMLEditor.RangeUpdaterRef(),
&invisibleTrailingWhiteSpaceRangeAtEnd);
AutoTransactionsConserveSelection dontChangeMySelection(aHTMLEditor);
nsresult rv = aHTMLEditor.ReplaceTextWithTransaction(
MOZ_KnownLive(
*atNBSPReplacedWithASCIIWhiteSpace.ContainerAs<Text>()),
atNBSPReplacedWithASCIIWhiteSpace.Offset(), 1, u" "_ns);
if (MOZ_UNLIKELY(NS_FAILED(rv))) {
Result<InsertTextResult, nsresult> replaceTextResult =
aHTMLEditor.ReplaceTextWithTransaction(
MOZ_KnownLive(
*atNBSPReplacedWithASCIIWhiteSpace.ContainerAs<Text>()),
atNBSPReplacedWithASCIIWhiteSpace.Offset(), 1, u" "_ns);
if (MOZ_UNLIKELY(replaceTextResult.isErr())) {
NS_WARNING("HTMLEditor::ReplaceTextWithTransaction() failed failed");
return Err(rv);
return replaceTextResult.propagateErr();
}
// Ignore caret suggestion because there was
// AutoTransactionsConserveSelection.
replaceTextResult.unwrap().IgnoreCaretPointSuggestion();
// Don't refer the following variables anymore unless tracking the
// change.
atNBSPReplaceableWithSP.Clear();
@ -3038,26 +3045,30 @@ nsresult WhiteSpaceVisibilityKeeper::ReplaceTextAndRemoveEmptyTextNodes(
MOZ_ASSERT(aRangeToReplace.EndRef().IsSetAndValid());
MOZ_ASSERT(aRangeToReplace.StartRef().IsBefore(aRangeToReplace.EndRef()));
AutoTransactionsConserveSelection dontChangeMySelection(aHTMLEditor);
nsresult rv = aHTMLEditor.ReplaceTextWithTransaction(
MOZ_KnownLive(*aRangeToReplace.StartRef().ContainerAs<Text>()),
aRangeToReplace.StartRef().Offset(),
aRangeToReplace.InSameContainer()
? aRangeToReplace.EndRef().Offset() -
aRangeToReplace.StartRef().Offset()
: aRangeToReplace.StartRef().ContainerAs<Text>()->TextLength() -
aRangeToReplace.StartRef().Offset(),
aReplaceString);
if (NS_FAILED(rv)) {
Result<InsertTextResult, nsresult> replaceTextResult =
aHTMLEditor.ReplaceTextWithTransaction(
MOZ_KnownLive(*aRangeToReplace.StartRef().ContainerAs<Text>()),
aRangeToReplace.StartRef().Offset(),
aRangeToReplace.InSameContainer()
? aRangeToReplace.EndRef().Offset() -
aRangeToReplace.StartRef().Offset()
: aRangeToReplace.StartRef().ContainerAs<Text>()->TextLength() -
aRangeToReplace.StartRef().Offset(),
aReplaceString);
if (MOZ_UNLIKELY(replaceTextResult.isErr())) {
NS_WARNING("HTMLEditor::ReplaceTextWithTransaction() failed");
return rv;
return replaceTextResult.propagateErr();
}
// Ignore caret suggestion because there was
// AutoTransactionsConserveSelection.
replaceTextResult.unwrap().IgnoreCaretPointSuggestion();
if (aRangeToReplace.InSameContainer()) {
return NS_OK;
}
rv = aHTMLEditor.DeleteTextAndTextNodesWithTransaction(
AutoTransactionsConserveSelection dontChangeMySelection(aHTMLEditor);
nsresult rv = aHTMLEditor.DeleteTextAndTextNodesWithTransaction(
EditorDOMPointInText::AtEndOf(
*aRangeToReplace.StartRef().ContainerAs<Text>()),
aRangeToReplace.EndRef(),
@ -3114,7 +3125,7 @@ nsresult WhiteSpaceVisibilityKeeper::NormalizeVisibleWhiteSpacesAt(
atEndOfVisibleWhiteSpaces);
if (!atPreviousCharOfEndOfVisibleWhiteSpaces.IsSet() ||
atPreviousCharOfEndOfVisibleWhiteSpaces.IsEndOfContainer() ||
// If the NBSP is never replaced from an ASCII white-space, we cannod
// If the NBSP is never replaced from an ASCII white-space, we cannot
// replace it with an ASCII white-space.
!atPreviousCharOfEndOfVisibleWhiteSpaces.IsCharCollapsibleNBSP()) {
return NS_OK;
@ -3201,27 +3212,17 @@ nsresult WhiteSpaceVisibilityKeeper::NormalizeVisibleWhiteSpacesAt(
if (MOZ_UNLIKELY(insertBRElementResult.isErr())) {
NS_WARNING(
"HTMLEditor::InsertBRElement(WithTransaction::Yes) failed");
return insertBRElementResult.unwrapErr();
return insertBRElementResult.propagateErr();
}
// XXX Is this intentional selection change?
nsresult rv = insertBRElementResult.inspect().SuggestCaretPointTo(
aHTMLEditor, {SuggestCaret::OnlyIfHasSuggestion,
SuggestCaret::OnlyIfTransactionsAllowedToDoIt,
SuggestCaret::AndIgnoreTrivialError});
if (NS_FAILED(rv)) {
NS_WARNING("CreateElementResult::SuggestCaretPointTo() failed");
return rv;
}
NS_WARNING_ASSERTION(
rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
"CreateElementResult::SuggestCaretPointTo() failed, but ignored");
MOZ_ASSERT(insertBRElementResult.inspect().GetNewNode());
// Ignore caret suggestion because the caller must want to restore
// `Selection` due to the purpose of this method.
insertBRElementResult.unwrap().IgnoreCaretPointSuggestion();
atPreviousCharOfEndOfVisibleWhiteSpaces =
textFragmentData.GetPreviousEditableCharPoint(
atEndOfVisibleWhiteSpaces);
if (MOZ_UNLIKELY(NS_WARN_IF(
!atPreviousCharOfEndOfVisibleWhiteSpaces.IsSet()))) {
if (NS_WARN_IF(!atPreviousCharOfEndOfVisibleWhiteSpaces.IsSet())) {
return NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE;
}
atPreviousCharOfPreviousCharOfEndOfVisibleWhiteSpaces =
@ -3256,14 +3257,19 @@ nsresult WhiteSpaceVisibilityKeeper::NormalizeVisibleWhiteSpacesAt(
(followedByVisibleContent || followedByBRElement) &&
!visibleWhiteSpaces.StartsFromPreformattedLineBreak()) {
MOZ_ASSERT(!followedByPreformattedLineBreak);
AutoTransactionsConserveSelection dontChangeMySelection(aHTMLEditor);
nsresult rv = aHTMLEditor.ReplaceTextWithTransaction(
MOZ_KnownLive(
*atPreviousCharOfEndOfVisibleWhiteSpaces.ContainerAs<Text>()),
atPreviousCharOfEndOfVisibleWhiteSpaces.Offset(), 1, u" "_ns);
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
"HTMLEditor::ReplaceTextWithTransaction() failed");
return rv;
Result<InsertTextResult, nsresult> replaceTextResult =
aHTMLEditor.ReplaceTextWithTransaction(
MOZ_KnownLive(*atPreviousCharOfEndOfVisibleWhiteSpaces
.ContainerAs<Text>()),
atPreviousCharOfEndOfVisibleWhiteSpaces.Offset(), 1, u" "_ns);
if (MOZ_UNLIKELY(replaceTextResult.isErr())) {
NS_WARNING("HTMLEditor::ReplaceTextWithTransaction() failed");
return replaceTextResult.propagateErr();
}
// Ignore caret suggestion because the caller must want to restore
// `Selection` due to the purpose of this method.
replaceTextResult.unwrap().IgnoreCaretPointSuggestion();
return NS_OK;
}
}
// If the text node is not preformatted, and the NBSP is followed by a <br>
@ -3290,7 +3296,6 @@ nsresult WhiteSpaceVisibilityKeeper::NormalizeVisibleWhiteSpacesAt(
textFragmentData.GetFirstASCIIWhiteSpacePointCollapsedTo(
atPreviousCharOfPreviousCharOfEndOfVisibleWhiteSpaces,
nsIEditor::eNone);
AutoTransactionsConserveSelection dontChangeMySelection(aHTMLEditor);
uint32_t numberOfASCIIWhiteSpacesInStartNode =
atFirstASCIIWhiteSpace.ContainerAs<Text>() ==
atPreviousCharOfEndOfVisibleWhiteSpaces.ContainerAs<Text>()
@ -3305,18 +3310,23 @@ nsresult WhiteSpaceVisibilityKeeper::NormalizeVisibleWhiteSpacesAt(
atPreviousCharOfEndOfVisibleWhiteSpaces.ContainerAs<Text>()
? 1
: 0);
nsresult rv = aHTMLEditor.ReplaceTextWithTransaction(
MOZ_KnownLive(*atFirstASCIIWhiteSpace.ContainerAs<Text>()),
atFirstASCIIWhiteSpace.Offset(), replaceLengthInStartNode,
textFragmentData.StartsFromPreformattedLineBreak() &&
textFragmentData.EndsByPreformattedLineBreak()
? u"\x00A0\x00A0"_ns
: (textFragmentData.EndsByPreformattedLineBreak() ? u" \x00A0"_ns
: u"\x00A0 "_ns));
if (NS_FAILED(rv)) {
Result<InsertTextResult, nsresult> replaceTextResult =
aHTMLEditor.ReplaceTextWithTransaction(
MOZ_KnownLive(*atFirstASCIIWhiteSpace.ContainerAs<Text>()),
atFirstASCIIWhiteSpace.Offset(), replaceLengthInStartNode,
textFragmentData.StartsFromPreformattedLineBreak() &&
textFragmentData.EndsByPreformattedLineBreak()
? u"\x00A0\x00A0"_ns
: (textFragmentData.EndsByPreformattedLineBreak()
? u" \x00A0"_ns
: u"\x00A0 "_ns));
if (MOZ_UNLIKELY(replaceTextResult.isErr())) {
NS_WARNING("HTMLEditor::ReplaceTextWithTransaction() failed");
return rv;
return replaceTextResult.propagateErr();
}
// Ignore caret suggestion because the caller must want to restore
// `Selection` due to the purpose of this method.
replaceTextResult.unwrap().IgnoreCaretPointSuggestion();
if (atFirstASCIIWhiteSpace.GetContainer() ==
atPreviousCharOfEndOfVisibleWhiteSpaces.GetContainer()) {
@ -3326,7 +3336,7 @@ nsresult WhiteSpaceVisibilityKeeper::NormalizeVisibleWhiteSpacesAt(
// We need to remove the following unnecessary ASCII white-spaces and
// NBSP at atPreviousCharOfEndOfVisibleWhiteSpaces because we collapsed them
// into the start node.
rv = aHTMLEditor.DeleteTextAndTextNodesWithTransaction(
nsresult rv = aHTMLEditor.DeleteTextAndTextNodesWithTransaction(
EditorDOMPointInText::AtEndOf(
*atFirstASCIIWhiteSpace.ContainerAs<Text>()),
atPreviousCharOfEndOfVisibleWhiteSpaces.NextPoint(),
@ -3387,16 +3397,20 @@ nsresult WhiteSpaceVisibilityKeeper::NormalizeVisibleWhiteSpacesAt(
atPreviousCharOfEndOfVisibleWhiteSpaces.NextPoint();
}
AutoTransactionsConserveSelection dontChangeMySelection(aHTMLEditor);
Result<EditorDOMPoint, nsresult> result =
Result<CaretPoint, nsresult> caretPointOrError =
aHTMLEditor.DeleteTextAndNormalizeSurroundingWhiteSpaces(
startToDelete, endToDelete,
HTMLEditor::TreatEmptyTextNodes::KeepIfContainerOfRangeBoundaries,
HTMLEditor::DeleteDirection::Forward);
NS_WARNING_ASSERTION(
!result.isOk(),
"HTMLEditor::DeleteTextAndNormalizeSurroundingWhiteSpaces() failed");
return result.isErr() ? result.unwrapErr() : NS_OK;
if (MOZ_UNLIKELY(caretPointOrError.isErr())) {
NS_WARNING(
"HTMLEditor::DeleteTextAndNormalizeSurroundingWhiteSpace() failed");
return caretPointOrError.unwrapErr();
}
// Ignore caret suggestion because the caller must want to restore
// `Selection` due to the purpose of this method.
caretPointOrError.unwrap().IgnoreCaretPointSuggestion();
return NS_OK;
}
EditorDOMPointInText WSRunScanner::TextFragmentData::

View File

@ -1533,8 +1533,10 @@ class WhiteSpaceVisibilityKeeper final {
const EditorDOMPoint& aCaretPoint);
/**
* NormalizeVisibleWhiteSpacesAt() tries to normalize visible white-space
* sequence around aPoint.
* Try to normalize visible white-space sequence around aPoint.
* This may collapse `Selection` after replaced text. Therefore, the callers
* of this need to restore `Selection` by themselves (this does not do it for
* performance reason of multiple calls).
*/
template <typename EditorDOMPointType>
[[nodiscard]] MOZ_CAN_RUN_SCRIPT static nsresult