Bug 1358025 - Part 2. Add SetText Transaction API. r?masayuki draft
authorMakoto Kato <m_kato@ga2.so-net.ne.jp>
Thu, 25 May 2017 14:30:50 +0900
changeset 584337 54778d9604081b07a1959a458e864e770724c18a
parent 580266 dcdfbe47fe9dac1e8b0a325c0be0a114ea28768d
child 584338 e24b0dee2de201eafbb7623547840d43b7ba17eb
push id60696
push userm_kato@ga2.so-net.ne.jp
push dateThu, 25 May 2017 09:12:31 +0000
reviewersmasayuki
bugs1358025
milestone55.0a1
Bug 1358025 - Part 2. Add SetText Transaction API. r?masayuki Actually, input.value setter behaviour (when editor has focus) is the following. - select all - delete selection - delete text node - insert text - create text node - create nsIFrame since we don't support lazy construction for editable element It is too expensive to change text. So I would like to change like the following when there is 1 text node only (normal case). If child nodes isn't 1 text node only, use original way. - set text on existed text node So, for this fast path, I would like to add SetText transaction API. MozReview-Commit-ID: A7bjXtCtSoB
editor/libeditor/EditorBase.cpp
editor/libeditor/EditorBase.h
editor/libeditor/SetTextTransaction.cpp
editor/libeditor/SetTextTransaction.h
editor/libeditor/TextEditRules.cpp
editor/libeditor/TextEditRules.h
editor/libeditor/TextEditor.cpp
editor/libeditor/moz.build
editor/nsIPlaintextEditor.idl
--- a/editor/libeditor/EditorBase.cpp
+++ b/editor/libeditor/EditorBase.cpp
@@ -17,16 +17,17 @@
 #include "DeleteRangeTransaction.h"     // for DeleteRangeTransaction
 #include "DeleteTextTransaction.h"      // for DeleteTextTransaction
 #include "EditAggregateTransaction.h"   // for EditAggregateTransaction
 #include "EditorEventListener.h"        // for EditorEventListener
 #include "InsertNodeTransaction.h"      // for InsertNodeTransaction
 #include "InsertTextTransaction.h"      // for InsertTextTransaction
 #include "JoinNodeTransaction.h"        // for JoinNodeTransaction
 #include "PlaceholderTransaction.h"     // for PlaceholderTransaction
+#include "SetTextTransaction.h"         // for SetTextTransaction
 #include "SplitNodeTransaction.h"       // for SplitNodeTransaction
 #include "StyleSheetTransactions.h"     // for AddStyleSheetTransaction, etc.
 #include "TextEditUtils.h"              // for TextEditUtils
 #include "mozInlineSpellChecker.h"      // for mozInlineSpellChecker
 #include "mozilla/CheckedInt.h"         // for CheckedInt
 #include "mozilla/EditorUtils.h"        // for AutoRules, etc.
 #include "mozilla/EditTransactionBase.h" // for EditTransactionBase
 #include "mozilla/FlushType.h"          // for FlushType::Frames
@@ -2643,27 +2644,89 @@ EditorBase::NotifyDocumentListeners(
     }
     default:
       NS_NOTREACHED("Unknown notification");
   }
 
   return rv;
 }
 
+nsresult
+EditorBase::SetTextImpl(const nsAString& aString, Text& aCharData)
+{
+  RefPtr<SetTextTransaction> transaction =
+    CreateTxnForSetText(aString, aCharData);
+  if (NS_WARN_IF(!transaction)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  uint32_t length = aCharData.Length();
+
+  AutoRules beginRulesSniffing(this, EditAction::setText,
+                               nsIEditor::eNext);
+
+  // Let listeners know what's up
+  {
+    AutoActionListenerArray listeners(mActionListeners);
+    for (auto& listener : listeners) {
+      if (length) {
+        listener->WillDeleteText(
+          static_cast<nsIDOMCharacterData*>(aCharData.AsDOMNode()), 0,
+          length);
+      }
+      if (!aString.IsEmpty()) {
+        listener->WillInsertText(
+          static_cast<nsIDOMCharacterData*>(aCharData.AsDOMNode()), 0,
+          aString);
+      }
+    }
+  }
+
+  nsresult rv = DoTransaction(transaction);
+
+  // Let listeners know what happened
+  {
+    AutoActionListenerArray listeners(mActionListeners);
+    for (auto& listener : listeners) {
+      if (length) {
+        listener->DidDeleteText(
+          static_cast<nsIDOMCharacterData*>(aCharData.AsDOMNode()), 0,
+          length, rv);
+      }
+      if (!aString.IsEmpty()) {
+        listener->DidInsertText(
+          static_cast<nsIDOMCharacterData*>(aCharData.AsDOMNode()), 0,
+          aString, rv);
+      }
+    }
+  }
+
+  return rv;
+}
+
 already_AddRefed<InsertTextTransaction>
 EditorBase::CreateTxnForInsertText(const nsAString& aStringToInsert,
                                    Text& aTextNode,
                                    int32_t aOffset)
 {
   RefPtr<InsertTextTransaction> transaction =
     new InsertTextTransaction(aTextNode, aOffset, aStringToInsert, *this,
                               &mRangeUpdater);
   return transaction.forget();
 }
 
+already_AddRefed<SetTextTransaction>
+EditorBase::CreateTxnForSetText(const nsAString& aString,
+                                Text& aTextNode)
+{
+  RefPtr<SetTextTransaction> transaction =
+    new SetTextTransaction(aTextNode, aString, *this, &mRangeUpdater);
+  return transaction.forget();
+}
+
 nsresult
 EditorBase::DeleteText(nsGenericDOMDataNode& aCharData,
                        uint32_t aOffset,
                        uint32_t aLength)
 {
   RefPtr<DeleteTextTransaction> transaction =
     CreateTxnForDeleteText(aCharData, aOffset, aLength);
   NS_ENSURE_STATE(transaction);
--- a/editor/libeditor/EditorBase.h
+++ b/editor/libeditor/EditorBase.h
@@ -67,16 +67,17 @@ enum class EditAction : int32_t
 
   // text commands
   insertText         = 2000,
   insertIMEText      = 2001,
   deleteSelection    = 2002,
   setTextProperty    = 2003,
   removeTextProperty = 2004,
   outputText         = 2005,
+  setText            = 2006,
 
   // html only action
   insertBreak         = 3000,
   makeList            = 3001,
   indent              = 3002,
   outdent             = 3003,
   align               = 3004,
   makeBasicBlock      = 3005,
@@ -111,16 +112,17 @@ class DeleteTextTransaction;
 class EditAggregateTransaction;
 class EditTransactionBase;
 class ErrorResult;
 class HTMLEditor;
 class InsertNodeTransaction;
 class InsertTextTransaction;
 class JoinNodeTransaction;
 class RemoveStyleSheetTransaction;
+class SetTextTransaction;
 class SplitNodeTransaction;
 class TextComposition;
 class TextEditor;
 struct EditorDOMPoint;
 
 namespace dom {
 class DataTransfer;
 class Element;
@@ -199,16 +201,20 @@ public:
 
   virtual nsresult InsertTextImpl(const nsAString& aStringToInsert,
                                   nsCOMPtr<nsINode>* aInOutNode,
                                   int32_t* aInOutOffset,
                                   nsIDocument* aDoc);
   nsresult InsertTextIntoTextNodeImpl(const nsAString& aStringToInsert,
                                       Text& aTextNode, int32_t aOffset,
                                       bool aSuppressIME = false);
+
+  nsresult SetTextImpl(const nsAString& aString,
+                       Text& aTextNode);
+
   NS_IMETHOD DeleteSelectionImpl(EDirection aAction,
                                  EStripWrappers aStripWrappers);
 
   already_AddRefed<Element> DeleteSelectionAndCreateElement(nsIAtom& aTag);
 
   /**
    * Helper routines for node/parent manipulations.
    */
@@ -349,16 +355,19 @@ protected:
   /**
    * Create a transaction for inserting aStringToInsert into aTextNode.  Never
    * returns null.
    */
   already_AddRefed<mozilla::InsertTextTransaction>
     CreateTxnForInsertText(const nsAString& aStringToInsert, Text& aTextNode,
                            int32_t aOffset);
 
+  already_AddRefed<SetTextTransaction>
+    CreateTxnForSetText(const nsAString& aString, Text& aTextNode);
+
   /**
    * Never returns null.
    */
   already_AddRefed<mozilla::CompositionTransaction>
     CreateTxnForComposition(const nsAString& aStringToInsert);
 
   /**
    * Create a transaction for adding a style sheet.
new file mode 100644
--- /dev/null
+++ b/editor/libeditor/SetTextTransaction.cpp
@@ -0,0 +1,109 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 sw=2 et tw=78: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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 "SetTextTransaction.h"
+
+#include "mozilla/DebugOnly.h"          // DebugOnly
+#include "mozilla/EditorBase.h"         // mEditorBase
+#include "mozilla/SelectionState.h"     // RangeUpdater
+#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
+
+namespace mozilla {
+
+using namespace dom;
+
+SetTextTransaction::SetTextTransaction(Text& aTextNode,
+                                       const nsAString& aStringToSet,
+                                       EditorBase& aEditorBase,
+                                       RangeUpdater* aRangeUpdater)
+  : mTextNode(&aTextNode)
+  , mStringToSet(aStringToSet)
+  , mEditorBase(&aEditorBase)
+  , mRangeUpdater(aRangeUpdater)
+{
+}
+
+SetTextTransaction::~SetTextTransaction()
+{
+}
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(SetTextTransaction, EditTransactionBase,
+                                   mEditorBase,
+                                   mTextNode)
+
+NS_IMPL_ADDREF_INHERITED(SetTextTransaction, EditTransactionBase)
+NS_IMPL_RELEASE_INHERITED(SetTextTransaction, EditTransactionBase)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(SetTextTransaction)
+  if (aIID.Equals(NS_GET_IID(SetTextTransaction))) {
+    foundInterface = static_cast<nsITransaction*>(this);
+  } else
+NS_INTERFACE_MAP_END_INHERITING(EditTransactionBase)
+
+
+NS_IMETHODIMP
+SetTextTransaction::DoTransaction()
+{
+  if (NS_WARN_IF(!mEditorBase) || NS_WARN_IF(!mTextNode)) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  nsresult rv = mTextNode->GetData(mPreviousData);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = mTextNode->SetData(mStringToSet);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  // Only set selection to insertion point if editor gives permission
+  if (mEditorBase->GetShouldTxnSetSelection()) {
+    RefPtr<Selection> selection = mEditorBase->GetSelection();
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return NS_ERROR_NULL_POINTER;
+    }
+    DebugOnly<nsresult> rv =
+      selection->Collapse(mTextNode, mStringToSet.Length());
+    NS_ASSERTION(NS_SUCCEEDED(rv),
+                 "Selection could not be collapsed after insert");
+  }
+  mRangeUpdater->SelAdjDeleteText(mTextNode, 0, mPreviousData.Length());
+  mRangeUpdater->SelAdjInsertText(*mTextNode, 0, mStringToSet);
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+SetTextTransaction::UndoTransaction()
+{
+  return mTextNode->SetData(mPreviousData);
+}
+
+NS_IMETHODIMP
+SetTextTransaction::Merge(nsITransaction* aTransaction,
+                          bool* aDidMerge)
+{
+  // Set out param default value
+  *aDidMerge = false;
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+SetTextTransaction::GetTxnDescription(nsAString& aString)
+{
+  aString.AssignLiteral("SetTextTransaction: ");
+  aString += mStringToSet;
+  return NS_OK;
+}
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/editor/libeditor/SetTextTransaction.h
@@ -0,0 +1,80 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 sw=2 et tw=78: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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/. */
+
+#ifndef mozilla_SetTextTransaction_h
+#define mozilla_SetTextTransaction_h
+
+#include "mozilla/EditTransactionBase.h"  // base class
+#include "nsCycleCollectionParticipant.h" // various macros
+#include "nsID.h"                       // NS_DECLARE_STATIC_IID_ACCESSOR
+#include "nsISupportsImpl.h"            // NS_DECL_ISUPPORTS_INHERITED
+#include "nsString.h"                   // nsString members
+#include "nscore.h"                     // NS_IMETHOD, nsAString
+
+class nsITransaction;
+
+#define NS_SETTEXTTXN_IID \
+{ 0x568bac0b, 0xa42a, 0x4150, \
+  { 0xbd, 0x90, 0x34, 0xd0, 0x2f, 0x32, 0x74, 0x2e } }
+
+namespace mozilla {
+
+class EditorBase;
+class RangeUpdater;
+
+namespace dom {
+class Text;
+} // namespace dom
+
+/**
+ * A transaction that inserts text into a content node.
+ */
+class SetTextTransaction final : public EditTransactionBase
+{
+public:
+  NS_DECLARE_STATIC_IID_ACCESSOR(NS_SETTEXTTXN_IID)
+
+  /**
+   * @param aTextNode       The text content node.
+   * @param aString         The new text to insert.
+   * @param aEditorBase     Used to get and set the selection.
+   * @param aRangeUpdater   The range updater
+   */
+  SetTextTransaction(dom::Text& aTextNode,
+                     const nsAString& aString, EditorBase& aEditorBase,
+                     RangeUpdater* aRangeUpdater);
+
+  NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(SetTextTransaction,
+                                           EditTransactionBase)
+
+  NS_DECL_EDITTRANSACTIONBASE
+
+  NS_IMETHOD Merge(nsITransaction* aTransaction, bool* aDidMerge) override;
+
+private:
+  virtual ~SetTextTransaction();
+
+  // The Text node to operate upon.
+  RefPtr<dom::Text> mTextNode;
+
+  // The text to insert into mTextNode at mOffset.
+  nsString mStringToSet;
+
+  // The previous text for undo
+  nsString mPreviousData;
+
+  // The editor, which we'll need to get the selection.
+  RefPtr<EditorBase> mEditorBase;
+
+  RangeUpdater* mRangeUpdater;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(SetTextTransaction, NS_SETTEXTTXN_IID)
+
+} // namespace mozilla
+
+#endif // #ifndef mozilla_SetTextTransaction_h
--- a/editor/libeditor/TextEditRules.cpp
+++ b/editor/libeditor/TextEditRules.cpp
@@ -29,18 +29,20 @@
 #include "nsIDOMNode.h"
 #include "nsIDOMNodeFilter.h"
 #include "nsIDOMText.h"
 #include "nsNameSpaceManager.h"
 #include "nsINode.h"
 #include "nsIPlaintextEditor.h"
 #include "nsISupportsBase.h"
 #include "nsLiteralString.h"
+#include "nsTextNode.h"
 #include "nsUnicharUtils.h"
 #include "nsIHTMLCollection.h"
+#include "nsPrintfCString.h"
 
 namespace mozilla {
 
 using namespace dom;
 
 #define CANCEL_OPERATION_IF_READONLY_OR_DISABLED \
   if (IsReadonly() || IsDisabled()) \
   {                     \
@@ -258,16 +260,20 @@ TextEditRules::WillDoAction(Selection* a
     case EditAction::insertBreak:
       UndefineCaretBidiLevel(aSelection);
       return WillInsertBreak(aSelection, aCancel, aHandled, info->maxLength);
     case EditAction::insertText:
     case EditAction::insertIMEText:
       UndefineCaretBidiLevel(aSelection);
       return WillInsertText(info->action, aSelection, aCancel, aHandled,
                             info->inString, info->outString, info->maxLength);
+    case EditAction::setText:
+      UndefineCaretBidiLevel(aSelection);
+      return WillSetText(*aSelection, aCancel, aHandled, info->inString,
+                         info->maxLength);
     case EditAction::deleteSelection:
       return WillDeleteSelection(aSelection, info->collapsedAction,
                                  aCancel, aHandled);
     case EditAction::undo:
       return WillUndo(aSelection, aCancel, aHandled);
     case EditAction::redo:
       return WillRedo(aSelection, aCancel, aHandled);
     case EditAction::setTextProperty:
@@ -303,16 +309,18 @@ TextEditRules::DidDoAction(Selection* aS
   TextRulesInfo* info = static_cast<TextRulesInfo*>(aInfo);
 
   switch (info->action) {
     case EditAction::insertBreak:
       return DidInsertBreak(aSelection, aResult);
     case EditAction::insertText:
     case EditAction::insertIMEText:
       return DidInsertText(aSelection, aResult);
+    case EditAction::setText:
+      return DidSetText(*aSelection, aResult);
     case EditAction::deleteSelection:
       return DidDeleteSelection(aSelection, info->collapsedAction, aResult);
     case EditAction::undo:
       return DidUndo(aSelection, aResult);
     case EditAction::redo:
       return DidRedo(aSelection, aResult);
     case EditAction::setTextProperty:
       return DidSetTextProperty(aSelection, aResult);
@@ -762,16 +770,128 @@ TextEditRules::WillInsertText(EditAction
 nsresult
 TextEditRules::DidInsertText(Selection* aSelection,
                              nsresult aResult)
 {
   return DidInsert(aSelection, aResult);
 }
 
 nsresult
+TextEditRules::WillSetText(Selection& aSelection,
+                           bool* aCancel,
+                           bool* aHandled,
+                           const nsAString* aString,
+                           int32_t aMaxLength)
+{
+  MOZ_ASSERT(aCancel);
+  MOZ_ASSERT(aHandled);
+  MOZ_ASSERT(aString);
+
+  CANCEL_OPERATION_IF_READONLY_OR_DISABLED
+
+  *aHandled = false;
+  *aCancel = false;
+
+  if (NS_WARN_IF(!mTextEditor)) {
+    return NS_ERROR_FAILURE;
+  }
+  RefPtr<TextEditor> textEditor = mTextEditor;
+
+  if (!IsPlaintextEditor() || textEditor->IsIMEComposing() ||
+      aMaxLength != -1) {
+    // SetTextTransaction only supports plain text editor without IME.
+    return NS_OK;
+  }
+
+  if (IsPasswordEditor() && LookAndFeel::GetEchoPassword() &&
+      !DontEchoPassword()) {
+    // Echo password timer will implement on InsertText.
+    return NS_OK;
+  }
+
+  WillInsert(aSelection, aCancel);
+  // we want to ignore result of WillInsert()
+  *aCancel = false;
+
+  RefPtr<Element> rootElement = textEditor->GetRoot();
+  uint32_t count = rootElement->GetChildCount();
+
+  // handles only when there is only one node and it's a text node, or empty.
+
+  if (count > 1) {
+    return NS_OK;
+  }
+
+  nsAutoString tString(*aString);
+
+  if (IsPasswordEditor()) {
+    mPasswordText.Assign(tString);
+    FillBufWithPWChars(&tString, tString.Length());
+  } else if (IsSingleLineEditor()) {
+    HandleNewLines(tString, textEditor->mNewlineHandling);
+  }
+
+  if (!count) {
+    if (tString.IsEmpty()) {
+      *aHandled = true;
+      return NS_OK;
+    }
+    RefPtr<nsIDocument> doc = textEditor->GetDocument();
+    if (NS_WARN_IF(!doc)) {
+      return NS_OK;
+    }
+    RefPtr<nsTextNode> newNode = doc->CreateTextNode(tString);
+    if (NS_WARN_IF(!newNode)) {
+      return NS_OK;
+    }
+    nsresult rv = textEditor->InsertNode(*newNode, *rootElement, 0);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+    *aHandled = true;
+
+    ASSERT_PASSWORD_LENGTHS_EQUAL();
+
+    return NS_OK;
+  }
+
+  nsINode* curNode = rootElement->GetFirstChild();
+  if (NS_WARN_IF(!EditorBase::IsTextNode(curNode))) {
+    return NS_OK;
+  }
+  if (tString.IsEmpty()) {
+    nsresult rv = textEditor->DeleteNode(curNode);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    *aHandled = true;
+    return NS_OK;
+  }
+
+  nsresult rv = textEditor->SetTextImpl(tString, *curNode->GetAsText());
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  *aHandled = true;
+
+  ASSERT_PASSWORD_LENGTHS_EQUAL();
+
+  return NS_OK;
+}
+
+nsresult
+TextEditRules::DidSetText(Selection& aSelection,
+                          nsresult aResult)
+{
+  return NS_OK;
+}
+
+nsresult
 TextEditRules::WillSetTextProperty(Selection* aSelection,
                                    bool* aCancel,
                                    bool* aHandled)
 {
   if (!aSelection || !aCancel || !aHandled) {
     return NS_ERROR_NULL_POINTER;
   }
 
--- a/editor/libeditor/TextEditRules.h
+++ b/editor/libeditor/TextEditRules.h
@@ -123,16 +123,23 @@ protected:
                           nsAString* outString,
                           int32_t aMaxLength);
   nsresult DidInsertText(Selection* aSelection, nsresult aResult);
 
   nsresult WillInsertBreak(Selection* aSelection, bool* aCancel,
                            bool* aHandled, int32_t aMaxLength);
   nsresult DidInsertBreak(Selection* aSelection, nsresult aResult);
 
+  nsresult WillSetText(Selection& aSelection,
+                       bool* aCancel,
+                       bool* aHandled,
+                       const nsAString* inString,
+                       int32_t aMaxLength);
+  nsresult DidSetText(Selection& aSelection, nsresult aResult);
+
   void WillInsert(Selection& aSelection, bool* aCancel);
   nsresult DidInsert(Selection* aSelection, nsresult aResult);
 
   nsresult WillDeleteSelection(Selection* aSelection,
                                nsIEditor::EDirection aCollapsedAction,
                                bool* aCancel,
                                bool* aHandled);
   nsresult DidDeleteSelection(Selection* aSelection,
--- a/editor/libeditor/TextEditor.cpp
+++ b/editor/libeditor/TextEditor.cpp
@@ -761,16 +761,74 @@ TextEditor::InsertLineBreak()
 
   if (!cancel) {
     // post-process, always called if WillInsertBreak didn't return cancel==true
     rv = rules->DidDoAction(selection, &ruleInfo, rv);
   }
   return rv;
 }
 
+NS_IMETHODIMP
+TextEditor::SetText(const nsAString& aString)
+{
+  if (NS_WARN_IF(!mRules)) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
+
+  // Protect the edit rules object from dying
+  nsCOMPtr<nsIEditRules> rules(mRules);
+
+  // delete placeholder txns merge.
+  AutoPlaceHolderBatch batch(this, nullptr);
+  AutoRules beginRulesSniffing(this, EditAction::setText, nsIEditor::eNext);
+
+  // pre-process
+  RefPtr<Selection> selection = GetSelection();
+  if (NS_WARN_IF(!selection)) {
+    return NS_ERROR_NULL_POINTER;
+  }
+  TextRulesInfo ruleInfo(EditAction::setText);
+  ruleInfo.inString = &aString;
+  ruleInfo.maxLength = mMaxTextLength;
+
+  bool cancel;
+  bool handled;
+  nsresult rv = rules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+  if (cancel) {
+    return NS_OK;
+  }
+  if (!handled) {
+    // 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()) {
+      // if it's empty, don't select entire doc - that would select
+      // the bogus node
+      Element* rootElement = GetRoot();
+      if (NS_WARN_IF(!rootElement)) {
+        return NS_ERROR_FAILURE;
+      }
+      rv = selection->Collapse(rootElement, 0);
+    } else {
+      rv = EditorBase::SelectEntireDocument(selection);
+    }
+    if (NS_SUCCEEDED(rv)) {
+      if (aString.IsEmpty()) {
+        rv = DeleteSelection(eNone, eStrip);
+      } else {
+        rv = InsertText(aString);
+      }
+    }
+  }
+  // post-process
+  return rules->DidDoAction(selection, &ruleInfo, rv);
+}
+
 nsresult
 TextEditor::BeginIMEComposition(WidgetCompositionEvent* aEvent)
 {
   NS_ENSURE_TRUE(!mComposition, NS_OK);
 
   if (IsPasswordEditor()) {
     NS_ENSURE_TRUE(mRules, NS_ERROR_NULL_POINTER);
     // Protect the edit rules object from dying
--- a/editor/libeditor/moz.build
+++ b/editor/libeditor/moz.build
@@ -59,16 +59,17 @@ UNIFIED_SOURCES += [
     'HTMLTableEditor.cpp',
     'HTMLURIRefObject.cpp',
     'InsertNodeTransaction.cpp',
     'InsertTextTransaction.cpp',
     'InternetCiter.cpp',
     'JoinNodeTransaction.cpp',
     'PlaceholderTransaction.cpp',
     'SelectionState.cpp',
+    'SetTextTransaction.cpp',
     'SplitNodeTransaction.cpp',
     'StyleSheetTransactions.cpp',
     'TextEditor.cpp',
     'TextEditorDataTransfer.cpp',
     'TextEditorTest.cpp',
     'TextEditRules.cpp',
     'TextEditRulesBidi.cpp',
     'TextEditUtils.cpp',
--- a/editor/nsIPlaintextEditor.idl
+++ b/editor/nsIPlaintextEditor.idl
@@ -98,15 +98,23 @@ interface nsIPlaintextEditor : nsISuppor
    * If the selection is not collapsed, the selection is deleted
    * and the insertion takes place at the resulting collapsed selection.
    *
    * @param aString   the string to be inserted
    */
    void insertText(in DOMString aStringToInsert);
 
   /**
+   * Replace existed string with a string.
+   * This is fast path to replace all string when using single line control.
+   *
+   * @ param aString   the string to be set
+   */
+  [noscript] void setText(in DOMString aString);
+
+  /**
    * Insert a line break into the content model.
    * The interpretation of a break is up to the implementation:
    * it may enter a character, split a node in the tree, etc.
    * This may be more efficient than calling InsertText with a newline.
    */
   void insertLineBreak();
 };