Bug 1815383 - part 1: Make `InsertTextTransaction::DoTransaction` stop updating `Selection` directly r=m_kato

And also this patch makes its only user,
`EditorBase::InsertTextIntoTextNodeWithTransaction`, and its only caller,
`EditorBase::InsertTextWithTransaction`, return `InsertTextResult` for
returning both end of inserted text and caret point suggestion.  Note that
if it's for IME composition, `CompositionTransaction` needs to update
`Selection` directly.  Therefore, caret point may be unset under composition
to updating `Selection` to wrong point (it seems that
`TextEditor::HandleInsertText` can be simplified later because of this change).

Depends on D169044

Differential Revision: https://phabricator.services.mozilla.com/D169744
This commit is contained in:
Masayuki Nakano 2023-02-17 08:25:24 +00:00
Родитель 2aa6aa8213
Коммит b1b8ae7fb7
11 изменённых файлов: 230 добавлений и 202 удалений

Просмотреть файл

@ -2748,14 +2748,9 @@ EditorDOMPointType EditorBase::FindBetterInsertionPoint(
return aPoint;
}
Result<EditorDOMPoint, nsresult> EditorBase::InsertTextWithTransaction(
Result<InsertTextResult, nsresult> EditorBase::InsertTextWithTransaction(
Document& aDocument, const nsAString& aStringToInsert,
const EditorDOMPoint& aPointToInsert) {
MOZ_ASSERT(
ShouldHandleIMEComposition() || !AllowsTransactionsToChangeSelection(),
"caller must have already used AutoTransactionsConserveSelection "
"if this is not for updating composition string");
if (NS_WARN_IF(!aPointToInsert.IsSet())) {
return Err(NS_ERROR_INVALID_ARG);
}
@ -2763,7 +2758,7 @@ Result<EditorDOMPoint, nsresult> EditorBase::InsertTextWithTransaction(
MOZ_ASSERT(aPointToInsert.IsSetAndValid());
if (!ShouldHandleIMEComposition() && aStringToInsert.IsEmpty()) {
return aPointToInsert;
return InsertTextResult();
}
// In some cases, the node may be the anonymous div element or a padding
@ -2785,7 +2780,6 @@ Result<EditorDOMPoint, nsresult> EditorBase::InsertTextWithTransaction(
}
if (ShouldHandleIMEComposition()) {
CheckedUint32 newOffset;
if (!pointToInsert.IsInTextNode()) {
// create a text node
RefPtr<nsTextNode> newTextNode = CreateTextNode(u""_ns);
@ -2799,62 +2793,27 @@ Result<EditorDOMPoint, nsresult> EditorBase::InsertTextWithTransaction(
NS_WARNING("EditorBase::InsertNodeWithTransaction() failed");
return insertTextNodeResult.propagateErr();
}
nsresult rv = insertTextNodeResult.inspect().SuggestCaretPointTo(
*this, {SuggestCaret::OnlyIfHasSuggestion,
SuggestCaret::OnlyIfTransactionsAllowedToDoIt,
SuggestCaret::AndIgnoreTrivialError});
if (NS_FAILED(rv)) {
NS_WARNING("CreateTextResult::SuggestCaretPointTo() failed");
return Err(rv);
}
NS_WARNING_ASSERTION(
rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
"CreateTextResult::SuggestCaretPointTo() failed, but ignored");
insertTextNodeResult.unwrap().IgnoreCaretPointSuggestion();
pointToInsert.Set(newTextNode, 0u);
newOffset = aStringToInsert.Length();
} else {
newOffset = aStringToInsert.Length();
newOffset += pointToInsert.Offset();
if (NS_WARN_IF(!newOffset.isValid())) {
return Err(NS_ERROR_FAILURE);
}
}
nsresult rv = InsertTextIntoTextNodeWithTransaction(
aStringToInsert, pointToInsert.AsInText());
if (MOZ_UNLIKELY(Destroyed())) {
NS_WARNING(
"EditorBase::InsertTextIntoTextNodeWithTransaction() caused "
"destroying the editor");
return Err(NS_ERROR_EDITOR_DESTROYED);
}
if (NS_FAILED(rv)) {
NS_WARNING("EditorBase::InsertTextIntoTextNodeWithTransaction() failed");
return Err(rv);
}
return EditorDOMPoint(pointToInsert.GetContainer(), newOffset.value());
Result<InsertTextResult, nsresult> insertTextResult =
InsertTextIntoTextNodeWithTransaction(aStringToInsert,
pointToInsert.AsInText());
NS_WARNING_ASSERTION(
insertTextResult.isOk(),
"EditorBase::InsertTextIntoTextNodeWithTransaction() failed");
return insertTextResult;
}
if (pointToInsert.IsInTextNode()) {
CheckedUint32 newOffset = aStringToInsert.Length();
newOffset += pointToInsert.Offset();
if (NS_WARN_IF(!newOffset.isValid())) {
return Err(NS_ERROR_FAILURE);
}
// we are inserting text into an existing text node.
nsresult rv = InsertTextIntoTextNodeWithTransaction(
aStringToInsert, EditorDOMPointInText(pointToInsert.ContainerAs<Text>(),
pointToInsert.Offset()));
if (MOZ_UNLIKELY(Destroyed())) {
NS_WARNING(
"EditorBase::InsertTextIntoTextNodeWithTransaction() caused "
"destroying the editor");
return Err(NS_ERROR_EDITOR_DESTROYED);
}
if (NS_FAILED(rv)) {
NS_WARNING("EditorBase::InsertTextIntoTextNodeWithTransaction() failed");
return Err(rv);
}
return EditorDOMPoint(pointToInsert.GetContainer(), newOffset.value());
Result<InsertTextResult, nsresult> insertTextResult =
InsertTextIntoTextNodeWithTransaction(aStringToInsert,
pointToInsert.AsInText());
NS_WARNING_ASSERTION(
insertTextResult.isOk(),
"EditorBase::InsertTextIntoTextNodeWithTransaction() failed");
return insertTextResult;
}
// we are inserting text into a non-text node. first we have to create a
@ -2870,19 +2829,12 @@ Result<EditorDOMPoint, nsresult> EditorBase::InsertTextWithTransaction(
NS_WARNING("EditorBase::InsertNodeWithTransaction() failed");
return Err(insertTextNodeResult.unwrapErr());
}
nsresult rv = insertTextNodeResult.inspect().SuggestCaretPointTo(
*this, {SuggestCaret::OnlyIfHasSuggestion,
SuggestCaret::OnlyIfTransactionsAllowedToDoIt,
SuggestCaret::AndIgnoreTrivialError});
if (NS_FAILED(rv)) {
NS_WARNING("CreateTextResult::SuggestCaretPointTo() failed");
return Err(rv);
insertTextNodeResult.unwrap().IgnoreCaretPointSuggestion();
if (NS_WARN_IF(!newTextNode->IsInComposedDoc())) {
return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
}
NS_WARNING_ASSERTION(
rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
"CreateTextResult::SuggestCaretPointTo() failed, but ignored");
return EditorDOMPoint(insertTextNodeResult.inspect().GetNewNode(),
aStringToInsert.Length());
return InsertTextResult(EditorDOMPointInText::AtEndOf(*newTextNode),
EditorDOMPoint::AtEndOf(*newTextNode));
}
static bool TextFragmentBeginsWithStringAtOffset(
@ -2939,18 +2891,16 @@ EditorBase::ComputeInsertedRange(const EditorDOMPointInText& aInsertedPoint,
return {EditorDOMPointInText(), EditorDOMPointInText()};
}
nsresult EditorBase::InsertTextIntoTextNodeWithTransaction(
Result<InsertTextResult, nsresult>
EditorBase::InsertTextIntoTextNodeWithTransaction(
const nsAString& aStringToInsert,
const EditorDOMPointInText& aPointToInsert, bool aSuppressIME) {
const EditorDOMPointInText& aPointToInsert) {
MOZ_ASSERT(IsEditActionDataAvailable());
MOZ_ASSERT(aPointToInsert.IsSetAndValid());
RefPtr<EditTransactionBase> transaction;
bool isIMETransaction = false;
// aSuppressIME is used when editor must insert text, yet this text is not
// part of the current IME operation. Example: adjusting white-space around an
// IME insertion.
if (ShouldHandleIMEComposition() && !aSuppressIME) {
if (ShouldHandleIMEComposition()) {
transaction =
CompositionTransaction::Create(*this, aStringToInsert, aPointToInsert);
isIMETransaction = true;
@ -2967,6 +2917,9 @@ nsresult EditorBase::InsertTextIntoTextNodeWithTransaction(
"EditorBase::DoTransactionInternal() failed");
EndUpdateViewBatch(__FUNCTION__);
// Don't check whether we've been destroyed here because we need to notify
// listeners and observers below even if we've already destroyed.
auto pointToInsert = [&]() -> EditorDOMPointInText {
if (!isIMETransaction) {
return aPointToInsert;
@ -2980,6 +2933,10 @@ nsresult EditorBase::InsertTextIntoTextNodeWithTransaction(
mComposition->GetContainerTextNode()->TextDataLength()));
}();
EditorDOMPointInText endOfInsertedText(
pointToInsert.ContainerAs<Text>(),
pointToInsert.Offset() + aStringToInsert.Length());
if (IsHTMLEditor()) {
auto [begin, end] = ComputeInsertedRange(pointToInsert, aStringToInsert);
if (begin.IsSet() && end.IsSet()) {
@ -2991,6 +2948,7 @@ nsresult EditorBase::InsertTextIntoTextNodeWithTransaction(
// IME since non-ASCII character may be inserted into it in most cases.
pointToInsert.ContainerAs<Text>()->MarkAsMaybeModifiedFrequently();
}
// XXX Should we update endOfInsertedText here?
}
// let listeners know what happened
@ -2998,9 +2956,9 @@ nsresult EditorBase::InsertTextIntoTextNodeWithTransaction(
for (auto& listener : mActionListeners.Clone()) {
// TODO: might need adaptation because of mutation event listeners called
// during `DoTransactionInternal`.
DebugOnly<nsresult> rvIgnored =
listener->DidInsertText(pointToInsert.ContainerAs<Text>(),
pointToInsert.Offset(), aStringToInsert, rv);
DebugOnly<nsresult> rvIgnored = listener->DidInsertText(
pointToInsert.ContainerAs<Text>(),
static_cast<int32_t>(pointToInsert.Offset()), aStringToInsert, rv);
NS_WARNING_ASSERTION(
NS_SUCCEEDED(rvIgnored),
"nsIEditActionListener::DidInsertText() failed, but ignored");
@ -3020,20 +2978,27 @@ nsresult EditorBase::InsertTextIntoTextNodeWithTransaction(
if (IsHTMLEditor() && isIMETransaction && mComposition) {
RefPtr<Text> textNode = mComposition->GetContainerTextNode();
if (textNode && !textNode->Length()) {
nsresult rv = DeleteNodeWithTransaction(*textNode);
if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) {
NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
return rv;
rv = DeleteNodeWithTransaction(*textNode);
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
"EditorBase::DeleteNodeTransaction() failed");
if (MOZ_LIKELY(!textNode->IsInComposedDoc())) {
mComposition->OnTextNodeRemoved();
}
NS_WARNING_ASSERTION(
NS_SUCCEEDED(rv),
"EditorBase::DeleteNodeWithTransaction() failed, but ignored");
mComposition->OnTextNodeRemoved();
static_cast<CompositionTransaction*>(transaction.get())->MarkFixed();
}
}
return rv;
if (NS_WARN_IF(Destroyed())) {
return Err(NS_ERROR_EDITOR_DESTROYED);
}
InsertTextTransaction* const insertTextTransaction =
transaction->GetAsInsertTextTransaction();
return insertTextTransaction
? InsertTextResult(std::move(endOfInsertedText),
insertTextTransaction
->SuggestPointToPutCaret<EditorDOMPoint>())
: InsertTextResult(std::move(endOfInsertedText));
}
nsresult EditorBase::NotifyDocumentListeners(

Просмотреть файл

@ -1665,39 +1665,28 @@ class EditorBase : public nsIEditor,
const nsAString& aStringToInsert, SelectionHandling aSelectionHandling);
/**
* InsertTextWithTransaction() inserts aStringToInsert to aPointToInsert or
* better insertion point around it. If aPointToInsert isn't in a text node,
* this method looks for the nearest point in a text node with
* FindBetterInsertionPoint(). If there is no text node, this creates
* new text node and put aStringToInsert to it.
* Insert aStringToInsert to aPointToInsert or better insertion point around
* it. If aPointToInsert isn't in a text node, this method looks for the
* nearest point in a text node with FindBetterInsertionPoint(). If there is
* no text node, this creates new text node and put aStringToInsert to it.
*
* @param aDocument The document of this editor.
* @param aStringToInsert The string to insert.
* @param aPointToInsert The point to insert aStringToInsert.
* Must be valid DOM point.
* @return If succeeded, returns the point after inserted
* aStringToInsert. So, when this method actually
* inserts string, returns a point in the text node.
* Otherwise, returns aPointToInsert.
*/
[[nodiscard]] MOZ_CAN_RUN_SCRIPT virtual Result<EditorDOMPoint, nsresult>
[[nodiscard]] MOZ_CAN_RUN_SCRIPT virtual Result<InsertTextResult, nsresult>
InsertTextWithTransaction(Document& aDocument,
const nsAString& aStringToInsert,
const EditorDOMPoint& aPointToInsert);
/**
* InsertTextIntoTextNodeWithTransaction() inserts aStringToInsert into
* aOffset of aTextNode with transaction.
*
* @param aStringToInsert String to be inserted.
* @param aPointToInsert The insertion point.
* @param aSuppressIME true if it's not a part of IME composition.
* E.g., adjusting white-spaces during composition.
* false, otherwise.
* Insert aStringToInsert to aPointToInsert.
*/
MOZ_CAN_RUN_SCRIPT nsresult InsertTextIntoTextNodeWithTransaction(
[[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<InsertTextResult, nsresult>
InsertTextIntoTextNodeWithTransaction(
const nsAString& aStringToInsert,
const EditorDOMPointInText& aPointToInsert, bool aSuppressIME = false);
const EditorDOMPointInText& aPointToInsert);
/**
* SetTextNodeWithoutTransaction() is optimized path to set new value to

Просмотреть файл

@ -75,11 +75,6 @@ using EditorRawDOMPointInText = EditorDOMPointBase<dom::Text*, nsIContent*>;
******************************************************************************/
class AutoPendingStyleCacheArray; // mozilla/PendingStyles.h
class AutoSelectionRangeArray; // EditorUtils.h
class CaretPoint; // EditorUtils.h
class ChangeStyleTransaction; // ChangeStyleTransaction.h
class CSSEditUtils; // CSSEditUtils.h
class EditActionResult; // EditorUtils.h
class EditTransactionBase; // mozilla/EditTransactionBase.h
class EditorBase; // mozilla/EditorBase.h
class HTMLEditor; // mozilla/HTMLEditor.h
@ -92,18 +87,24 @@ class SelectionState; // mozilla/SelectionState.h
class TextEditor; // mozilla/TextEditor.h
class AutoRangeArray; // AutoRangeArray.h
class AutoSelectionRangeArray; // EditorUtils.h
class CaretPoint; // EditorUtils.h
class ChangeAttributeTransaction; // ChangeAttributeTransaction.h
class ChangeStyleTransaction; // ChangeStyleTransaction.h
class CompositionTransaction; // CompositionTransaction.h
class CSSEditUtils; // CSSEditUtils.h
class DeleteContentTransactionBase; // DeleteContentTransactionBase.h
class DeleteMultipleRangesTransaction; // DeleteMultipleRangesTransaction.h
class DeleteNodeTransaction; // DeleteNodeTransaction.h
class DeleteRangeTransaction; // DeleteRangeTransaction.h
class DeleteTextTransaction; // DeleteTextTransaction.h
class EditActionResult; // EditorUtils.h
class EditAggregateTransaction; // EditAggregateTransaction.h
class EditorEventListener; // EditorEventListener.h
class EditResult; // HTMLEditHelpers.h
class HTMLEditorEventListener; // HTMLEditorEventListener.h
class InsertNodeTransaction; // InsertNodeTransaction.h
class InsertTextResult; // EditorUtils.h
class InsertTextTransaction; // InsertTextTransaction.h
class InterCiter; // InterCiter.h
class JoinNodesResult; // HTMLEditHelpers.h

Просмотреть файл

@ -254,6 +254,36 @@ class MOZ_STACK_CLASS CreateNodeResultBase final : public CaretPoint {
RefPtr<NodeType> mNode;
};
/**
* This is a result of inserting text. If the text inserted as a part of
* composition, this does not return CaretPoint. Otherwise, must return
* CaretPoint which is typically same as end of inserted text.
*/
class MOZ_STACK_CLASS InsertTextResult final : public CaretPoint {
public:
InsertTextResult() : CaretPoint(EditorDOMPoint()) {}
template <typename EditorDOMPointType>
explicit InsertTextResult(const EditorDOMPointType& aEndOfInsertedText)
: CaretPoint(EditorDOMPoint()),
mEndOfInsertedText(aEndOfInsertedText.template To<EditorDOMPoint>()) {}
explicit InsertTextResult(EditorDOMPointInText&& aEndOfInsertedText)
: CaretPoint(EditorDOMPoint()),
mEndOfInsertedText(std::move(aEndOfInsertedText)) {}
template <typename EditorDOMPointType>
InsertTextResult(EditorDOMPointInText&& aEndOfInsertedText,
const EditorDOMPointType& aCaretPoint)
: CaretPoint(aCaretPoint.template To<EditorDOMPoint>()),
mEndOfInsertedText(std::move(aEndOfInsertedText)) {}
[[nodiscard]] bool Handled() const { return mEndOfInsertedText.IsSet(); }
const EditorDOMPointInText& EndOfInsertedTextRef() const {
return mEndOfInsertedText;
}
private:
EditorDOMPointInText mEndOfInsertedText;
};
/***************************************************************************
* stack based helper class for calling EditorBase::EndTransaction() after
* EditorBase::BeginTransaction(). This shouldn't be used in editor classes

Просмотреть файл

@ -1148,13 +1148,24 @@ Result<EditActionResult, nsresult> HTMLEditor::HandleInsertText(
// Right now the WhiteSpaceVisibilityKeeper code bails on empty strings,
// but IME needs the InsertTextWithTransaction() call to still happen
// since empty strings are meaningful there.
Result<EditorDOMPoint, nsresult> insertTextResult =
Result<InsertTextResult, nsresult> insertTextResult =
InsertTextWithTransaction(*document, aInsertionString,
compositionStartPoint);
if (MOZ_UNLIKELY(insertTextResult.isErr())) {
NS_WARNING("HTMLEditor::InsertTextWithTransaction() failed");
return insertTextResult.propagateErr();
}
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 EditActionResult::HandledResult();
}
@ -1273,14 +1284,22 @@ Result<EditActionResult, nsresult> HTMLEditor::HandleInsertText(
"to different point "
"by mutation observer");
} else {
Result<EditorDOMPoint, nsresult> insertTextResult =
Result<InsertTextResult, nsresult> insertTextResult =
InsertTextWithTransaction(*document, subStr, currentPoint);
if (MOZ_UNLIKELY(insertTextResult.isErr())) {
NS_WARNING("HTMLEditor::InsertTextWithTransaction() failed");
return insertTextResult.propagateErr();
}
currentPoint = insertTextResult.inspect();
pointToInsert = insertTextResult.unwrap();
// Ignore the caret suggestion because of `dontChangeMySelection`
// above.
insertTextResult.inspect().IgnoreCaretPointSuggestion();
if (insertTextResult.inspect().Handled()) {
pointToInsert = currentPoint = insertTextResult.unwrap()
.EndOfInsertedTextRef()
.To<EditorDOMPoint>();
} else {
pointToInsert = currentPoint;
}
}
}
} else {
@ -2311,13 +2330,19 @@ Result<EditorDOMPoint, nsresult> HTMLEditor::HandleInsertLinefeed(
{
AutoTrackDOMPoint trackingInsertingPosition(RangeUpdaterRef(),
&pointToInsert);
Result<EditorDOMPoint, nsresult> insertTextResult =
Result<InsertTextResult, nsresult> insertTextResult =
InsertTextWithTransaction(*document, u"\n"_ns, pointToInsert);
if (MOZ_UNLIKELY(insertTextResult.isErr())) {
NS_WARNING("HTMLEditor::InsertTextWithTransaction() failed");
return insertTextResult;
return insertTextResult.propagateErr();
}
pointToPutCaret = insertTextResult.unwrap();
// Ignore the caret suggestion because of `dontChangeMySelection` above.
insertTextResult.inspect().IgnoreCaretPointSuggestion();
pointToPutCaret = insertTextResult.inspect().Handled()
? insertTextResult.unwrap()
.EndOfInsertedTextRef()
.To<EditorDOMPoint>()
: pointToInsert;
}
// Insert a padding <br> element at the end of the block element if there is

Просмотреть файл

@ -3862,13 +3862,24 @@ nsresult HTMLEditor::ReplaceTextWithTransaction(
if (NS_WARN_IF(!document)) {
return NS_ERROR_NOT_INITIALIZED;
}
Result<EditorDOMPoint, nsresult> insertTextResult =
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;
}
@ -3960,7 +3971,7 @@ nsresult HTMLEditor::ReplaceTextWithTransaction(
return NS_WARN_IF(Destroyed()) ? NS_ERROR_EDITOR_DESTROYED : rv;
}
Result<EditorDOMPoint, nsresult> HTMLEditor::InsertTextWithTransaction(
Result<InsertTextResult, nsresult> HTMLEditor::InsertTextWithTransaction(
Document& aDocument, const nsAString& aStringToInsert,
const EditorDOMPoint& aPointToInsert) {
if (NS_WARN_IF(!aPointToInsert.IsSet())) {

Просмотреть файл

@ -752,9 +752,10 @@ class HTMLEditor final : public EditorBase,
const nsAString& aStringToInsert);
/**
* InsertTextWithTransaction() inserts aStringToInsert at aPointToInsert.
* Insert aStringToInsert to aPointToInsert. If the point is not editable,
* this returns error.
*/
[[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditorDOMPoint, nsresult>
[[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<InsertTextResult, nsresult>
InsertTextWithTransaction(Document& aDocument,
const nsAString& aStringToInsert,
const EditorDOMPoint& aPointToInsert) final;

Просмотреть файл

@ -5,16 +5,18 @@
#include "InsertTextTransaction.h"
#include "ErrorList.h"
#include "mozilla/EditorBase.h" // mEditorBase
#include "mozilla/Logging.h"
#include "mozilla/SelectionState.h" // RangeUpdater
#include "mozilla/ToString.h"
#include "mozilla/dom/Selection.h" // Selection local var
#include "mozilla/dom/Text.h" // mTextNode
#include "nsAString.h" // nsAString parameter
#include "nsDebug.h" // for NS_ASSERTION, etc.
#include "nsError.h" // for NS_OK, etc.
#include "nsQueryObject.h" // for do_QueryObject
#include "nsAString.h" // nsAString parameter
#include "nsDebug.h" // for NS_ASSERTION, etc.
#include "nsError.h" // for NS_OK, etc.
#include "nsQueryObject.h" // for do_QueryObject
namespace mozilla {
@ -77,28 +79,9 @@ NS_IMETHODIMP InsertTextTransaction::DoTransaction() {
return error.StealNSResult();
}
// Only set selection to insertion point if editor gives permission
if (editorBase->AllowsTransactionsToChangeSelection()) {
RefPtr<Selection> selection = editorBase->GetSelection();
if (NS_WARN_IF(!selection)) {
return NS_ERROR_FAILURE;
}
DebugOnly<nsresult> rvIgnored = editorBase->CollapseSelectionTo(
EditorRawDOMPoint(textNode, mOffset + mStringToInsert.Length()));
NS_ASSERTION(NS_SUCCEEDED(rvIgnored),
"EditorBase::CollapseSelectionTo() failed, but ignored");
// Keep handling to adjust the ranges in the range updater even if the
// editor is destroyed.
} else {
// Do nothing - DOM Range gravity will adjust selection
}
// XXX Other transactions do not do this but its callers do.
// Why do this transaction do this by itself?
editorBase->RangeUpdaterRef().SelAdjInsertText(textNode, mOffset,
mStringToInsert.Length());
return MOZ_UNLIKELY(editorBase->Destroyed()) ? NS_ERROR_EDITOR_DESTROYED
: NS_OK;
return NS_OK;
}
NS_IMETHODIMP InsertTextTransaction::UndoTransaction() {
@ -121,7 +104,22 @@ NS_IMETHODIMP InsertTextTransaction::RedoTransaction() {
MOZ_LOG(GetLogModule(), LogLevel::Info,
("%p InsertTextTransaction::%s this=%s", this, __FUNCTION__,
ToString(*this).c_str()));
return DoTransaction();
nsresult rv = DoTransaction();
if (NS_FAILED(rv)) {
NS_WARNING("InsertTextTransaction::DoTransaction() failed");
return rv;
}
if (RefPtr<EditorBase> editorBase = mEditorBase) {
nsresult rv = editorBase->CollapseSelectionTo(
SuggestPointToPutCaret<EditorRawDOMPoint>());
if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
return NS_ERROR_EDITOR_DESTROYED;
}
NS_WARNING_ASSERTION(
NS_SUCCEEDED(rv),
"EditorBase::CollapseSelectionTo() failed, but ignored");
}
return NS_OK;
}
NS_IMETHODIMP InsertTextTransaction::Merge(nsITransaction* aOtherTransaction,

Просмотреть файл

@ -8,6 +8,7 @@
#include "EditTransactionBase.h" // base class
#include "EditorDOMPoint.h"
#include "EditorForwards.h"
#include "nsCycleCollectionParticipant.h" // various macros
@ -57,6 +58,14 @@ class InsertTextTransaction final : public EditTransactionBase {
*/
void GetData(nsString& aResult);
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 InsertTextTransaction& aTransaction);

Просмотреть файл

@ -3,6 +3,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "ErrorList.h"
#include "TextEditor.h"
#include "AutoRangeArray.h"
@ -218,49 +219,34 @@ TextEditor::InsertLineFeedCharacterAtSelection() {
return Err(NS_ERROR_NOT_INITIALIZED);
}
// Don't change my selection in sub-transactions.
AutoTransactionsConserveSelection dontChangeMySelection(*this);
// Insert a linefeed character.
Result<EditorDOMPoint, nsresult> insertTextResult =
Result<InsertTextResult, nsresult> insertTextResult =
InsertTextWithTransaction(*document, u"\n"_ns, pointToInsert);
if (MOZ_UNLIKELY(insertTextResult.isErr())) {
NS_WARNING("TextEditor::InsertTextWithTransaction(\"\\n\") failed");
return insertTextResult.propagateErr();
}
if (MOZ_UNLIKELY(!insertTextResult.inspect().IsSet())) {
NS_WARNING(
"EditorBase::InsertTextWithTransaction(\"\\n\") didn't return position "
"of inserted linefeed");
insertTextResult.inspect().IgnoreCaretPointSuggestion();
EditorDOMPoint pointToPutCaret = insertTextResult.inspect().Handled()
? insertTextResult.inspect()
.EndOfInsertedTextRef()
.To<EditorDOMPoint>()
: pointToInsert;
if (NS_WARN_IF(!pointToPutCaret.IsSetAndValid())) {
return Err(NS_ERROR_FAILURE);
}
// set the selection to the correct location
MOZ_ASSERT(insertTextResult.inspect().IsInTextNode(),
"After inserting text into a text node, insertTextResult should "
"return a point in a text node");
nsresult rv = CollapseSelectionTo(insertTextResult.inspect());
// XXX I don't think we still need this. This must have been required when
// `<textarea>` was implemented with text nodes and `<br>` elements.
// We want the caret to stick to the content on the "right". We want the
// caret to stick to whatever is past the break. This is because the break is
// on the same line we were on, but the next content will be on the following
// line.
pointToPutCaret.SetInterlinePosition(InterlinePosition::StartOfNextLine);
nsresult rv = CollapseSelectionTo(pointToPutCaret);
if (NS_FAILED(rv)) {
NS_WARNING("EditorBase::CollapseSelectionTo() failed");
return Err(rv);
}
// XXX I don't think we still need this. This must have been required when
// `<textarea>` was implemented with text nodes and `<br>` elements.
// see if we're at the end of the editor range
const auto endPoint = GetFirstSelectionEndPoint<EditorRawDOMPoint>();
if (endPoint == insertTextResult.inspect()) {
// SetInterlinePosition(true) means we want the caret to stick to the
// content on the "right". We want the caret to stick to whatever is
// past the break. This is because the break is on the same line we
// were on, but the next content will be on the following line.
DebugOnly<nsresult> rvIgnored =
SelectionRef().SetInterlinePosition(InterlinePosition::StartOfNextLine);
NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
"Selection::SetInterlinePosition(InterlinePosition::"
"StartOfNextLine) failed, but ignored");
}
return EditActionResult::HandledResult();
}
@ -475,33 +461,45 @@ Result<EditActionResult, nsresult> TextEditor::HandleInsertText(
compositionStartPoint.IsSet(),
"EditorBase::FindBetterInsertionPoint() failed, but ignored");
}
Result<EditorDOMPoint, nsresult> insertTextResult =
Result<InsertTextResult, nsresult> insertTextResult =
InsertTextWithTransaction(*document, insertionString,
compositionStartPoint);
if (MOZ_UNLIKELY(insertTextResult.isErr())) {
NS_WARNING("EditorBase::InsertTextWithTransaction() failed");
return insertTextResult.propagateErr();
}
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");
} else {
MOZ_ASSERT(aEditSubAction == EditSubAction::eInsertText);
// don't change my selection in subtransactions
AutoTransactionsConserveSelection dontChangeMySelection(*this);
Result<EditorDOMPoint, nsresult> insertTextResult =
Result<InsertTextResult, nsresult> insertTextResult =
InsertTextWithTransaction(*document, insertionString,
atStartOfSelection);
if (MOZ_UNLIKELY(insertTextResult.isErr())) {
NS_WARNING("EditorBase::InsertTextWithTransaction() failed");
return insertTextResult.propagateErr();
}
if (insertTextResult.inspect().IsSet()) {
// Ignore caret suggestion because there was
// AutoTransactionsConserveSelection.
insertTextResult.inspect().IgnoreCaretPointSuggestion();
if (insertTextResult.inspect().Handled()) {
// Make the caret attach to the inserted text, unless this text ends with
// a LF, in which case make the caret attach to the next line.
const bool endsWithLF =
!insertionString.IsEmpty() && insertionString.Last() == nsCRT::LF;
EditorDOMPoint pointToPutCaret = insertTextResult.unwrap();
EditorDOMPoint pointToPutCaret = insertTextResult.inspect()
.EndOfInsertedTextRef()
.To<EditorDOMPoint>();
pointToPutCaret.SetInterlinePosition(
endsWithLF ? InterlinePosition::StartOfNextLine
: InterlinePosition::EndOfLine);

Просмотреть файл

@ -1315,22 +1315,23 @@ Result<EditorDOMPoint, nsresult> WhiteSpaceVisibilityKeeper::ReplaceText(
return Err(NS_ERROR_UNEXPECTED);
}
OwningNonNull<Document> document = *aHTMLEditor.GetDocument();
Result<EditorDOMPoint, nsresult> insertTextResult =
Result<InsertTextResult, nsresult> insertTextResult =
aHTMLEditor.InsertTextWithTransaction(document, theString, pointToInsert);
if (MOZ_UNLIKELY(insertTextResult.isErr() && insertTextResult.inspectErr() ==
NS_ERROR_EDITOR_DESTROYED)) {
NS_WARNING(
"HTMLEditor::InsertTextWithTransaction() caused destroying the editor");
return Err(NS_ERROR_EDITOR_DESTROYED);
if (MOZ_UNLIKELY(insertTextResult.isErr())) {
if (MOZ_UNLIKELY(insertTextResult.inspectErr() ==
NS_ERROR_EDITOR_DESTROYED)) {
NS_WARNING("HTMLEditor::InsertTextWithTransaction() failed");
return Err(NS_ERROR_EDITOR_DESTROYED);
}
NS_WARNING("HTMLEditor::InsertTextWithTransaction() failed, but ignored");
// XXX Temporarily, set new insertion point to the original point.
return pointToInsert;
}
if (insertTextResult.isOk()) {
return insertTextResult.unwrap();
}
NS_WARNING("HTMLEditor::InsertTextWithTransaction() failed, but ignored");
// XXX Temporarily, set new insertion point to the original point.
return pointToInsert;
insertTextResult.inspect().IgnoreCaretPointSuggestion();
return insertTextResult.inspect().Handled() ? insertTextResult.inspect()
.EndOfInsertedTextRef()
.To<EditorDOMPoint>()
: pointToInsert;
}
// static