Bug 1574852 - part 109: Move TextEditRules::WillSetText() to TextEditor r=m_kato

And also renaming `EditorBase::SetTextImpl()`.

Differential Revision: https://phabricator.services.mozilla.com/D45492

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Masayuki Nakano 2019-09-14 15:12:03 +00:00
parent 96fff904a3
commit 87f73d9008
6 changed files with 120 additions and 140 deletions

View File

@ -2910,19 +2910,23 @@ nsresult EditorBase::NotifyDocumentListeners(
return rv;
}
nsresult EditorBase::SetTextImpl(const nsAString& aString, Text& aTextNode) {
nsresult EditorBase::SetTextNodeWithoutTransaction(const nsAString& aString,
Text& aTextNode) {
MOZ_ASSERT(IsEditActionDataAvailable());
MOZ_ASSERT(!AsHTMLEditor());
MOZ_ASSERT(IsPlaintextEditor());
MOZ_ASSERT(!IsUndoRedoEnabled());
const uint32_t length = aTextNode.Length();
AutoEditSubActionNotifier startToHandleEditSubAction(
*this, EditSubAction::eSetText, nsIEditor::eNext);
// Let listeners know what's up
if (!mActionListeners.IsEmpty() && length) {
AutoActionListenerArray listeners(mActionListeners);
for (auto& listener : listeners) {
listener->WillDeleteText(&aTextNode, 0, length);
if (NS_WARN_IF(Destroyed())) {
return NS_ERROR_EDITOR_DESTROYED;
}
}
}
@ -2936,13 +2940,13 @@ nsresult EditorBase::SetTextImpl(const nsAString& aString, Text& aTextNode) {
return rv;
}
{
// Create a nested scope to not overwrite rv from the outer scope.
DebugOnly<nsresult> rv =
SelectionRefPtr()->Collapse(&aTextNode, aString.Length());
NS_ASSERTION(NS_SUCCEEDED(rv),
"Selection could not be collapsed after insert");
DebugOnly<nsresult> rvIgnored =
SelectionRefPtr()->Collapse(&aTextNode, aString.Length());
if (NS_WARN_IF(Destroyed())) {
return NS_ERROR_EDITOR_DESTROYED;
}
NS_ASSERTION(NS_SUCCEEDED(rvIgnored),
"Selection::Collapse() failed, but ignored");
RangeUpdaterRef().SelAdjDeleteText(&aTextNode, 0, length);
RangeUpdaterRef().SelAdjInsertText(aTextNode, 0, aString);
@ -2951,9 +2955,11 @@ nsresult EditorBase::SetTextImpl(const nsAString& aString, Text& aTextNode) {
RefPtr<HTMLEditRules> htmlEditRules = mRules->AsHTMLEditRules();
if (length) {
htmlEditRules->DidDeleteText(aTextNode, 0, length);
MOZ_ASSERT(!Destroyed());
}
if (!aString.IsEmpty()) {
htmlEditRules->DidInsertText(aTextNode, 0, aString);
MOZ_ASSERT(!Destroyed());
}
}
@ -2963,14 +2969,20 @@ nsresult EditorBase::SetTextImpl(const nsAString& aString, Text& aTextNode) {
for (auto& listener : listeners) {
if (length) {
listener->DidDeleteText(&aTextNode, 0, length, rv);
if (NS_WARN_IF(Destroyed())) {
return NS_ERROR_EDITOR_DESTROYED;
}
}
if (!aString.IsEmpty()) {
listener->DidInsertText(&aTextNode, 0, aString, rv);
if (NS_WARN_IF(Destroyed())) {
return NS_ERROR_EDITOR_DESTROYED;
}
}
}
}
return rv;
return NS_OK;
}
nsresult EditorBase::DeleteTextWithTransaction(Text& aTextNode,

View File

@ -1144,8 +1144,13 @@ class EditorBase : public nsIEditor,
const nsAString& aStringToInsert, Text& aTextNode, int32_t aOffset,
bool aSuppressIME = false);
MOZ_CAN_RUN_SCRIPT nsresult SetTextImpl(const nsAString& aString,
Text& aTextNode);
/**
* SetTextNodeWithoutTransaction() is optimized path to set new value to
* the text node directly and without transaction. This is used when
* setting `<input>.value` and `<textarea>.value`.
*/
MOZ_CAN_RUN_SCRIPT MOZ_MUST_USE nsresult
SetTextNodeWithoutTransaction(const nsAString& aString, Text& aTextNode);
/**
* DeleteNodeWithTransaction() removes aNode from the DOM tree.

View File

@ -202,9 +202,6 @@ nsresult TextEditRules::WillDoAction(EditSubActionInfo& aInfo, bool* aCancel,
// my kingdom for dynamic cast
switch (aInfo.mEditSubAction) {
case EditSubAction::eSetText:
TextEditorRef().UndefineCaretBidiLevel();
return WillSetText(aCancel, aHandled, aInfo.inString, aInfo.maxLength);
case EditSubAction::eInsertQuotedText: {
CANCEL_OPERATION_IF_READONLY_OR_DISABLED
@ -224,6 +221,7 @@ nsresult TextEditRules::WillDoAction(EditSubActionInfo& aInfo, bool* aCancel,
case EditSubAction::eInsertLineBreak:
case EditSubAction::eInsertText:
case EditSubAction::eInsertTextComingFromIME:
case EditSubAction::eSetText:
case EditSubAction::eUndo:
case EditSubAction::eRedo:
MOZ_ASSERT_UNREACHABLE("This path should've been dead code");
@ -246,6 +244,7 @@ nsresult TextEditRules::DidDoAction(EditSubActionInfo& aInfo,
case EditSubAction::eInsertLineBreak:
case EditSubAction::eInsertText:
case EditSubAction::eInsertTextComingFromIME:
case EditSubAction::eSetText:
case EditSubAction::eUndo:
case EditSubAction::eRedo:
MOZ_ASSERT_UNREACHABLE("This path should've been dead code");
@ -679,41 +678,31 @@ EditActionResult TextEditor::HandleInsertText(
return EditActionHandled();
}
nsresult TextEditRules::WillSetText(bool* aCancel, bool* aHandled,
const nsAString* aString,
int32_t aMaxLength) {
MOZ_ASSERT(IsEditorDataAvailable());
MOZ_ASSERT(!mIsHTMLEditRules);
MOZ_ASSERT(aCancel);
MOZ_ASSERT(aHandled);
MOZ_ASSERT(aString);
MOZ_ASSERT(aString->FindChar(static_cast<char16_t>('\r')) == kNotFound);
EditActionResult TextEditor::SetTextWithoutTransaction(
const nsAString& aValue) {
MOZ_ASSERT(IsEditActionDataAvailable());
MOZ_ASSERT(!AsTextEditor());
MOZ_ASSERT(IsPlaintextEditor());
MOZ_ASSERT(!IsIMEComposing());
MOZ_ASSERT(!IsUndoRedoEnabled());
MOZ_ASSERT(GetEditAction() != EditAction::eReplaceText);
MOZ_ASSERT(mMaxTextLength < 0);
MOZ_ASSERT(aValue.FindChar(static_cast<char16_t>('\r')) == kNotFound);
UndefineCaretBidiLevel();
// XXX If we're setting value, shouldn't we keep setting the new value here?
CANCEL_OPERATION_IF_READONLY_OR_DISABLED
CANCEL_OPERATION_AND_RETURN_EDIT_ACTION_RESULT_IF_READONLY_OF_DISABLED
*aHandled = false;
*aCancel = false;
MaybeDoAutoPasswordMasking();
if (!IsPlaintextEditor() || TextEditorRef().IsIMEComposing() ||
TextEditorRef().IsUndoRedoEnabled() ||
TextEditorRef().GetEditAction() == EditAction::eReplaceText ||
aMaxLength != -1) {
// SetTextImpl only supports plain text editor without IME and
// when we don't need to make it undoable.
return NS_OK;
}
TextEditorRef().MaybeDoAutoPasswordMasking();
nsresult rv =
MOZ_KnownLive(TextEditorRef()).EnsureNoPaddingBRElementForEmptyEditor();
nsresult rv = EnsureNoPaddingBRElementForEmptyEditor();
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
return EditActionResult(rv);
}
RefPtr<Element> rootElement = TextEditorRef().GetRoot();
nsIContent* firstChild = rootElement->GetFirstChild();
RefPtr<Element> anonymousDivElement = GetRoot();
nsIContent* firstChild = anonymousDivElement->GetFirstChild();
// We can use this fast path only when:
// - we need to insert a text node.
@ -726,82 +715,73 @@ nsresult TextEditRules::WillSetText(bool* aCancel, bool* aHandled,
// that even if there is a padding <br> element for empty editor, it's
// already been removed by `EnsureNoPaddingBRElementForEmptyEditor()`. So,
// at here, there should be only one text node or no children.
if (firstChild &&
(!EditorBase::IsTextNode(firstChild) || firstChild->GetNextSibling())) {
return NS_OK;
if (firstChild && (!firstChild->IsText() || firstChild->GetNextSibling())) {
return EditActionIgnored();
}
} else {
// If we're a multiline text editor, i.e., <textarea>, there is a padding
// <br> element for empty last line followed by scrollbar/resizer elements.
// Otherwise, a text node is followed by them.
if (!firstChild) {
return NS_OK;
return EditActionIgnored();
}
if (EditorBase::IsTextNode(firstChild)) {
if (firstChild->IsText()) {
if (!firstChild->GetNextSibling() ||
!EditorBase::IsPaddingBRElementForEmptyLastLine(
*firstChild->GetNextSibling())) {
return NS_OK;
return EditActionIgnored();
}
} else if (!EditorBase::IsPaddingBRElementForEmptyLastLine(*firstChild)) {
return NS_OK;
return EditActionIgnored();
}
}
// XXX Password fields accept line breaks as normal characters with this code.
// Is this intentional?
nsAutoString tString(*aString);
nsAutoString sanitizedValue(aValue);
if (IsSingleLineEditor() && !IsPasswordEditor()) {
TextEditorRef().HandleNewLinesInStringForSingleLineEditor(tString);
HandleNewLinesInStringForSingleLineEditor(sanitizedValue);
}
if (!firstChild || !EditorBase::IsTextNode(firstChild)) {
if (tString.IsEmpty()) {
*aHandled = true;
return NS_OK;
if (!firstChild || !firstChild->IsText()) {
if (sanitizedValue.IsEmpty()) {
return EditActionHandled();
}
RefPtr<Document> doc = TextEditorRef().GetDocument();
if (NS_WARN_IF(!doc)) {
return NS_OK;
RefPtr<Document> document = GetDocument();
if (NS_WARN_IF(!document)) {
return EditActionIgnored();
}
RefPtr<nsTextNode> newNode = TextEditorRef().CreateTextNode(tString);
if (NS_WARN_IF(!newNode)) {
return NS_OK;
RefPtr<nsTextNode> newTextNode = CreateTextNode(sanitizedValue);
if (NS_WARN_IF(!newTextNode)) {
return EditActionIgnored();
}
nsresult rv = MOZ_KnownLive(TextEditorRef())
.InsertNodeWithTransaction(
*newNode, EditorDOMPoint(rootElement, 0));
if (NS_WARN_IF(!CanHandleEditAction())) {
return NS_ERROR_EDITOR_DESTROYED;
nsresult rv = InsertNodeWithTransaction(
*newTextNode, EditorDOMPoint(anonymousDivElement, 0));
if (NS_WARN_IF(Destroyed())) {
return EditActionResult(NS_ERROR_EDITOR_DESTROYED);
}
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
return EditActionResult(rv);
}
*aHandled = true;
return NS_OK;
return EditActionHandled();
}
// Even if empty text, we don't remove text node and set empty text
// for performance
// TODO: If new value is empty string, we should only remove it.
RefPtr<Text> textNode = firstChild->GetAsText();
if (MOZ_UNLIKELY(NS_WARN_IF(!textNode))) {
return NS_OK;
}
rv = MOZ_KnownLive(TextEditorRef()).SetTextImpl(tString, *textNode);
if (NS_WARN_IF(!CanHandleEditAction())) {
return NS_ERROR_EDITOR_DESTROYED;
return EditActionIgnored();
}
rv = SetTextNodeWithoutTransaction(sanitizedValue, *textNode);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
return EditActionResult(rv);
}
// If we replaced non-empty value with empty string, we need to delete the
// text node.
if (tString.IsEmpty() && !textNode->Length()) {
nsresult rv =
MOZ_KnownLive(TextEditorRef()).DeleteNodeWithTransaction(*textNode);
if (sanitizedValue.IsEmpty() && !textNode->Length()) {
nsresult rv = DeleteNodeWithTransaction(*textNode);
if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
return NS_ERROR_EDITOR_DESTROYED;
return EditActionResult(NS_ERROR_EDITOR_DESTROYED);
}
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
"DeleteNodeWithTransaction() failed, but ignored");
@ -814,8 +794,7 @@ nsresult TextEditRules::WillSetText(bool* aCancel, bool* aHandled,
"Selection::SetInterlinePoisition() failed");
}
*aHandled = true;
return NS_OK;
return EditActionHandled();
}
EditActionResult TextEditor::HandleDeleteSelection(

View File

@ -106,22 +106,6 @@ class TextEditRules {
// TextEditRules implementation methods
/**
* Called before setting text to the text editor.
* This method may actually set text to it. Therefore, this might cause
* destroying the text editor.
*
* @param aCancel Returns true if the operation is canceled.
* @param aHandled Returns true if the edit action is handled.
* @param inString String to be set.
* @param aMaxLength The maximum string length which the text editor
* allows to set.
*/
MOZ_CAN_RUN_SCRIPT
MOZ_MUST_USE nsresult WillSetText(bool* aCancel, bool* aHandled,
const nsAString* inString,
int32_t aMaxLength);
/**
* Creates a trailing break in the text doc if there is not one already.
*/

View File

@ -991,10 +991,8 @@ nsresult TextEditor::SetTextAsAction(const nsAString& aString,
AutoPlaceholderBatch treatAsOneTransaction(*this);
nsresult rv = SetTextAsSubAction(aString);
if (NS_WARN_IF(NS_FAILED(rv))) {
return EditorBase::ToGenericNSResult(rv);
}
return NS_OK;
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "SetTextAsSubAction() failed");
return EditorBase::ToGenericNSResult(rv);
}
nsresult TextEditor::ReplaceTextAsAction(const nsAString& aString,
@ -1019,10 +1017,8 @@ nsresult TextEditor::ReplaceTextAsAction(const nsAString& aString,
if (!aReplaceRange) {
nsresult rv = SetTextAsSubAction(aString);
if (NS_WARN_IF(NS_FAILED(rv))) {
return EditorBase::ToGenericNSResult(rv);
}
return NS_OK;
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "SetTextAsSubAction() failed");
return EditorBase::ToGenericNSResult(rv);
}
if (NS_WARN_IF(aString.IsEmpty() && aReplaceRange->Collapsed())) {
@ -1062,26 +1058,18 @@ nsresult TextEditor::SetTextAsSubAction(const nsAString& aString) {
return NS_ERROR_NOT_INITIALIZED;
}
// Protect the edit rules object from dying
RefPtr<TextEditRules> rules(mRules);
AutoEditSubActionNotifier startToHandleEditSubAction(
*this, EditSubAction::eSetText, nsIEditor::eNext);
EditSubActionInfo subActionInfo(EditSubAction::eSetText);
subActionInfo.inString = &aString;
subActionInfo.maxLength = mMaxTextLength;
if (IsPlaintextEditor() && !IsIMEComposing() && !IsUndoRedoEnabled() &&
GetEditAction() != EditAction::eReplaceText && mMaxTextLength < 0) {
EditActionResult result = SetTextWithoutTransaction(aString);
if (NS_WARN_IF(result.Failed()) || result.Canceled() || result.Handled()) {
return result.Rv();
}
}
bool cancel;
bool handled;
nsresult rv = rules->WillDoAction(subActionInfo, &cancel, &handled);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
if (cancel) {
return NS_OK;
}
if (!handled) {
{
// Note that do not notify selectionchange caused by selecting all text
// because it's preparation of our delete implementation so web apps
// shouldn't receive such selectionchange before the first mutation.
@ -1092,31 +1080,34 @@ nsresult TextEditor::SetTextAsSubAction(const nsAString& aString) {
return NS_ERROR_FAILURE;
}
// We want to select trailing BR node to remove all nodes to replace all,
// but TextEditor::SelectEntireDocument doesn't select that BR node.
if (rules->DocumentIsEmpty()) {
// We want to select trailing `<br>` element to remove all nodes to replace
// all, but TextEditor::SelectEntireDocument() doesn't select such `<br>`
// elements.
// XXX We should make ReplaceSelectionAsSubAction() take range. Then,
// we can saving the expensive cost of modifying `Selection` here.
nsresult rv;
if (mRules && mRules->DocumentIsEmpty()) {
rv = SelectionRefPtr()->Collapse(rootElement, 0);
NS_WARNING_ASSERTION(
NS_SUCCEEDED(rv),
"Failed to move caret to start of the editor root element");
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
"Selection::Collapse() failed, but ignored");
} else {
ErrorResult error;
SelectionRefPtr()->SelectAllChildren(*rootElement, error);
NS_WARNING_ASSERTION(
!error.Failed(),
"Failed to select all children of the editor root element");
"Selection::SelectAllChildren() failed, but ignored");
rv = error.StealNSResult();
}
if (NS_SUCCEEDED(rv)) {
rv = ReplaceSelectionAsSubAction(aString);
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
"Failed to replace selection with new string");
DebugOnly<nsresult> rvIgnored = ReplaceSelectionAsSubAction(aString);
NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
"ReplaceSelectionAsSubAction() failed, but ignored");
}
}
// post-process
rv = rules->DidDoAction(subActionInfo, rv);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
// Destroying AutoUpdateViewBatch may cause destroying us.
if (NS_WARN_IF(Destroyed())) {
return NS_ERROR_EDITOR_DESTROYED;
}
return NS_OK;
}

View File

@ -457,7 +457,8 @@ class TextEditor : public EditorBase,
*
* @ param aString The string to be set.
*/
MOZ_CAN_RUN_SCRIPT nsresult SetTextAsSubAction(const nsAString& aString);
MOZ_CAN_RUN_SCRIPT MOZ_MUST_USE nsresult
SetTextAsSubAction(const nsAString& aString);
/**
* ReplaceSelectionAsSubAction() replaces selection with aString.
@ -660,6 +661,14 @@ class TextEditor : public EditorBase,
EditActionResult ComputeValueFromTextNodeAndPaddingBRElement(
nsAString& aValue) const;
/**
* SetTextWithoutTransaction() is optimized method to set `<input>.value`
* and `<textarea>.value` to aValue without transaction. This must be
* called only when it's not `HTMLEditor` and undo/redo is disabled.
*/
MOZ_CAN_RUN_SCRIPT MOZ_MUST_USE EditActionResult
SetTextWithoutTransaction(const nsAString& aValue);
protected: // Called by helper classes.
virtual void OnStartToHandleTopLevelEditSubAction(
EditSubAction aEditSubAction, nsIEditor::EDirection aDirection) override;