Bug 1465702 - part 3: Make public methods of TextEditor create AutoEditActionDataSetter if necessary r=m_kato
authorMasayuki Nakano <masayuki@d-toybox.com>
Tue, 30 Oct 2018 09:59:33 +0000
changeset 499926 27ca7b49a11e0440721ab013b100578ac558b161
parent 499925 1c3326439c30247df655a1e35be2f216329a9d39
child 499927 986f21f1f5d5757812f6956aa9b8722c6a043842
push id10290
push userffxbld-merge
push dateMon, 03 Dec 2018 16:23:23 +0000
treeherdermozilla-beta@700bed2445e6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersm_kato
bugs1465702
milestone65.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1465702 - part 3: Make public methods of TextEditor create AutoEditActionDataSetter if necessary r=m_kato Differential Revision: https://phabricator.services.mozilla.com/D10007
editor/libeditor/EditAction.h
editor/libeditor/TextEditRules.h
editor/libeditor/TextEditor.cpp
editor/libeditor/TextEditor.h
editor/libeditor/TextEditorDataTransfer.cpp
--- a/editor/libeditor/EditAction.h
+++ b/editor/libeditor/EditAction.h
@@ -17,21 +17,94 @@ enum class EditAction
   // eNone indicates no edit action is being handled.
   eNone,
 
   // eNotEditing indicates that something is retrieved or initializing
   // something at creating, destroying or focus move etc, i.e., not edit
   // action is being handled but editor is doing something.
   eNotEditing,
 
+  // eInsertText indicates to insert some characters.
+  eInsertText,
+
+  // eInsertParagraphSeparator indicates to insert a paragraph separator such
+  // as <p>, <div> or just \n in TextEditor.
+  eInsertParagraphSeparator,
+
+  // eDeleteSelection indicates to delete selected content or content around
+  // caret if selection is collapsed.
+  eDeleteSelection,
+
+  // eDeleteBackward indicates to remove previous character element of caret.
+  // This may be set even when Selection is not collapsed.
+  eDeleteBackward,
+
+  // eDeleteForward indicates to remove next character or element of caret.
+  // This may be set even when Selection is not collapsed.
+  eDeleteForward,
+
+  // eDeleteWordBackward indicates to remove previous word.  If caret is in
+  // a word, remove characters between word start and caret.
+  // This may be set even when Selection is not collapsed.
+  eDeleteWordBackward,
+
+  // eDeleteWordForward indicates to remove next word.  If caret is in a
+  // word, remove characters between caret and word end.
+  // This may be set even when Selection is not collapsed.
+  eDeleteWordForward,
+
+  // eDeleteToBeginningOfSoftLine indicates to remove characters between
+  // caret and previous visual line break.
+  // This may be set even when Selection is not collapsed.
+  eDeleteToBeginningOfSoftLine,
+
+  // eDeleteToEndOfSoftLine indicates to remove characters between caret and
+  // next visual line break.
+  // This may be set even when Selection is not collapsed.
+  eDeleteToEndOfSoftLine,
+
+  // eStartComposition indicates that user starts composition.
+  eStartComposition,
+
+  // eUpdateComposition indicates that user updates composition with
+  // new composition string and IME selections.
+  eUpdateComposition,
+
+  // eCommitComposition indicates that user commits composition.
+  eCommitComposition,
+
+  // eEndComposition indicates that user ends composition.
+  eEndComposition,
+
+  // eUndo/eRedo indicate to undo/redo a transaction.
+  eUndo,
+  eRedo,
+
   // eSetTextDirection indicates that setting text direction (LTR or RTL).
   eSetTextDirection,
 
+  // eCut indicates to delete selected content and copy it to the clipboard.
+  eCut,
+
+  // eCopy indicates to copy selected content to the clipboard.
+  eCopy,
+
+  // ePaste indicates to paste clipboard data.
+  ePaste,
+
+  // eDrop indicates that user drops dragging item into the editor.
+  eDrop,
+
+  // eReplaceText indicates to replace a part of range in editor with
+  // specific text.  For example, user select a correct word in suggestions
+  // of spellchecker or a suggestion in list of autocomplete.
+  eReplaceText,
+
   // The following edit actions are not user's operation.  They are caused
-  // by if UI does something.
+  // by if UI does something or web apps does something with JS.
 
   // eUnknown indicates some special edit actions, e.g., batching of some
   // nsI*Editor method calls.  This shouldn't be set while handling a user
   // operation.
   eUnknown,
 
   // eSetAttribute indicates to set attribute value of an element node.
   eSetAttribute,
@@ -45,16 +118,29 @@ enum class EditAction
   // eDeleteNode indicates to remove a node form the tree.
   eRemoveNode,
 
   // eSplitNode indicates to split a node.
   eSplitNode,
 
   // eJoinNodes indicates to join 2 nodes.
   eJoinNodes,
+
+  // eSetCharacterSet indicates to set character-set of the document.
+  eSetCharacterSet,
+
+  // eSetWrapWidth indicates to set wrap width.
+  eSetWrapWidth,
+
+  // eSetText indicates to set new text of TextEditor, e.g., setting
+  // HTMLInputElement.value.
+  eSetText,
+
+  // eHidePassword indicates that editor hides password with mask characters.
+  eHidePassword,
 };
 
 // This is int32_t instead of int16_t because nsIInlineSpellChecker.idl's
 // spellCheckAfterEditorChange is defined to take it as a long.
 // TODO: Make each name eFoo and investigate whether the numeric values
 //       still have some meaning.
 enum class EditSubAction : int32_t
 {
--- a/editor/libeditor/TextEditRules.h
+++ b/editor/libeditor/TextEditRules.h
@@ -146,17 +146,18 @@ public:
 
   bool HasBogusNode()
   {
     return !!mBogusNode;
   }
 
   /**
    * HideLastPasswordInput() is called while Nodify() is calling
-   * TextEditor::HideLastPasswordInput().
+   * TextEditor::HideLastPasswordInput().  It guarantees that there is a
+   * AutoEditActionDataSetter instance in the editor.
    */
   MOZ_CAN_RUN_SCRIPT nsresult HideLastPasswordInput(Selection& aSelection);
 
 protected:
 
   void InitFields();
 
   // TextEditRules implementation methods
--- a/editor/libeditor/TextEditor.cpp
+++ b/editor/libeditor/TextEditor.cpp
@@ -205,30 +205,40 @@ nsresult
 TextEditor::EndEditorInit()
 {
   MOZ_ASSERT(mInitTriggerCounter > 0, "ended editor init before we began?");
   mInitTriggerCounter--;
   if (mInitTriggerCounter) {
     return NS_OK;
   }
 
+  AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing);
+  if (NS_WARN_IF(!editActionData.CanHandle())) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
+
   nsresult rv = InitRules();
   if (NS_FAILED(rv)) {
     return rv;
   }
   // Throw away the old transaction manager if this is not the first time that
   // we're initializing the editor.
   ClearUndoRedo();
   EnableUndoRedo();
   return NS_OK;
 }
 
 NS_IMETHODIMP
 TextEditor::SetDocumentCharacterSet(const nsACString& characterSet)
 {
+  AutoEditActionDataSetter editActionData(*this, EditAction::eSetCharacterSet);
+  if (NS_WARN_IF(!editActionData.CanHandle())) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
+
   nsresult rv = EditorBase::SetDocumentCharacterSet(characterSet);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Update META charset element.
   nsCOMPtr<nsIDocument> doc = GetDocument();
   if (NS_WARN_IF(!doc)) {
     return NS_ERROR_NOT_INITIALIZED;
   }
@@ -414,27 +424,39 @@ TextEditor::HandleKeyPressEvent(WidgetKe
   aKeyboardEvent->PreventDefault();
   nsAutoString str(aKeyboardEvent->mCharCode);
   return OnInputText(str);
 }
 
 nsresult
 TextEditor::OnInputText(const nsAString& aStringToInsert)
 {
+  AutoEditActionDataSetter editActionData(*this, EditAction::eInsertText);
+  if (NS_WARN_IF(!editActionData.CanHandle())) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
+
   AutoPlaceholderBatch treatAsOneTransaction(*this, *nsGkAtoms::TypingTxnName);
   nsresult rv = InsertTextAsSubAction(aStringToInsert);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
   return NS_OK;
 }
 
 nsresult
 TextEditor::OnInputParagraphSeparator()
 {
+  AutoEditActionDataSetter editActionData(
+                             *this,
+                             EditAction::eInsertParagraphSeparator);
+  if (NS_WARN_IF(!editActionData.CanHandle())) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
+
   AutoPlaceholderBatch treatAsOneTransaction(*this, *nsGkAtoms::TypingTxnName);
   nsresult rv = InsertParagraphSeparatorAsAction();
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
   return NS_OK;
 }
 
@@ -656,16 +678,43 @@ TextEditor::DeleteSelectionAsAction(EDir
   MOZ_ASSERT(aStripWrappers == eStrip || aStripWrappers == eNoStrip);
   // Showing this assertion is fine if this method is called by outside via
   // mutation event listener or something.  Otherwise, this is called by
   // wrong method.
   NS_ASSERTION(!mPlaceholderBatch,
     "Should be called only when this is the only edit action of the operation "
     "unless mutation event listener nests some operations");
 
+  EditAction editAction = EditAction::eDeleteSelection;
+  switch (aDirection) {
+    case nsIEditor::ePrevious:
+      editAction = EditAction::eDeleteBackward;
+      break;
+    case nsIEditor::eNext:
+      editAction = EditAction::eDeleteForward;
+      break;
+    case nsIEditor::ePreviousWord:
+      editAction = EditAction::eDeleteWordBackward;
+      break;
+    case nsIEditor::eNextWord:
+      editAction = EditAction::eDeleteWordForward;
+      break;
+    case nsIEditor::eToBeginningOfLine:
+      editAction = EditAction::eDeleteToBeginningOfSoftLine;
+      break;
+    case nsIEditor::eToEndOfLine:
+      editAction = EditAction::eDeleteToEndOfSoftLine;
+      break;
+  }
+
+  AutoEditActionDataSetter editActionData(*this, editAction);
+  if (NS_WARN_IF(!editActionData.CanHandle())) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
+
   // delete placeholder txns merge.
   AutoPlaceholderBatch treatAsOneTransaction(*this, *nsGkAtoms::DeleteTxnName);
   nsresult rv = DeleteSelectionAsSubAction(aDirection, aStripWrappers);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
   return NS_OK;
 }
@@ -962,16 +1011,21 @@ TextEditor::InsertTextAsAction(const nsA
 {
   // Showing this assertion is fine if this method is called by outside via
   // mutation event listener or something.  Otherwise, this is called by
   // wrong method.
   NS_ASSERTION(!mPlaceholderBatch,
     "Should be called only when this is the only edit action of the operation "
     "unless mutation event listener nests some operations");
 
+  AutoEditActionDataSetter editActionData(*this, EditAction::eInsertText);
+  if (NS_WARN_IF(!editActionData.CanHandle())) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
+
   AutoPlaceholderBatch treatAsOneTransaction(*this);
   nsresult rv = InsertTextAsSubAction(aStringToInsert);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
   return NS_OK;
 }
 
@@ -1026,16 +1080,23 @@ TextEditor::InsertTextAsSubAction(const 
     return rv;
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
 TextEditor::InsertLineBreak()
 {
+  AutoEditActionDataSetter editActionData(
+                             *this,
+                             EditAction::eInsertParagraphSeparator);
+  if (NS_WARN_IF(!editActionData.CanHandle())) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
+
   return InsertParagraphSeparatorAsAction();
 }
 
 nsresult
 TextEditor::InsertParagraphSeparatorAsAction()
 {
   if (!mRules) {
     return NS_ERROR_NOT_INITIALIZED;
@@ -1130,28 +1191,38 @@ TextEditor::InsertParagraphSeparatorAsAc
   return rv;
 }
 
 nsresult
 TextEditor::SetText(const nsAString& aString)
 {
   MOZ_ASSERT(aString.FindChar(static_cast<char16_t>('\r')) == kNotFound);
 
+  AutoEditActionDataSetter editActionData(*this, EditAction::eSetText);
+  if (NS_WARN_IF(!editActionData.CanHandle())) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
+
   AutoPlaceholderBatch treatAsOneTransaction(*this);
   nsresult rv = SetTextAsSubAction(aString);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
   return NS_OK;
 }
 
 nsresult
 TextEditor::ReplaceTextAsAction(const nsAString& aString,
                                 nsRange* aReplaceRange /* = nullptr */)
 {
+  AutoEditActionDataSetter editActionData(*this, EditAction::eReplaceText);
+  if (NS_WARN_IF(!editActionData.CanHandle())) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
+
   AutoPlaceholderBatch treatAsOneTransaction(*this);
 
   // This should emulates inserting text for better undo/redo behavior.
   AutoTopLevelEditSubActionNotifier maybeTopLevelEditSubAction(
                                       *this, EditSubAction::eInsertText,
                                       nsIEditor::eNext);
 
   if (!aReplaceRange) {
@@ -1166,29 +1237,24 @@ TextEditor::ReplaceTextAsAction(const ns
     return NS_OK;
   }
 
   // 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.
   AutoUpdateViewBatch preventSelectionChangeEvent(*this);
 
-  RefPtr<Selection> selection = GetSelection();
-  if (NS_WARN_IF(!selection)) {
-    return NS_ERROR_FAILURE;
-  }
-
   // Select the range but as far as possible, we should not create new range
   // even if it's part of special Selection.
-  nsresult rv = selection->RemoveAllRangesTemporarily();
+  nsresult rv = SelectionRefPtr()->RemoveAllRangesTemporarily();
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
   ErrorResult error;
-  selection->AddRange(*aReplaceRange, error);
+  SelectionRefPtr()->AddRange(*aReplaceRange, error);
   if (NS_WARN_IF(error.Failed())) {
     return error.StealNSResult();
   }
 
   rv = ReplaceSelectionAsSubAction(aString);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
@@ -1297,16 +1363,21 @@ TextEditor::EnsureComposition(WidgetComp
 
 nsresult
 TextEditor::OnCompositionStart(WidgetCompositionEvent& aCompositionStartEvent)
 {
   if (NS_WARN_IF(mComposition)) {
     return NS_OK;
   }
 
+  AutoEditActionDataSetter editActionData(*this, EditAction::eStartComposition);
+  if (NS_WARN_IF(!editActionData.CanHandle())) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
+
   if (IsPasswordEditor()) {
     if (NS_WARN_IF(!mRules)) {
       return NS_ERROR_FAILURE;
     }
     // Protect the edit rules object from dying
     RefPtr<TextEditRules> rules(mRules);
     rules->ResetIMETextPWBuf();
   }
@@ -1317,30 +1388,33 @@ TextEditor::OnCompositionStart(WidgetCom
 }
 
 nsresult
 TextEditor::OnCompositionChange(WidgetCompositionEvent& aCompsitionChangeEvent)
 {
   MOZ_ASSERT(aCompsitionChangeEvent.mMessage == eCompositionChange,
              "The event should be eCompositionChange");
 
+  EditAction editAction =
+    aCompsitionChangeEvent.IsFollowedByCompositionEnd() ?
+      EditAction::eCommitComposition : EditAction::eUpdateComposition;
+  AutoEditActionDataSetter editActionData(*this, editAction);
+  if (NS_WARN_IF(!editActionData.CanHandle())) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
+
   if (!EnsureComposition(aCompsitionChangeEvent)) {
     return NS_OK;
   }
 
   nsIPresShell* presShell = GetPresShell();
   if (NS_WARN_IF(!presShell)) {
     return NS_ERROR_NOT_INITIALIZED;
   }
 
-  RefPtr<Selection> selection = GetSelection();
-  if (NS_WARN_IF(!selection)) {
-    return NS_ERROR_FAILURE;
-  }
-
   // NOTE: TextComposition should receive selection change notification before
   //       CompositionChangeEventHandlingMarker notifies TextComposition of the
   //       end of handling compositionchange event because TextComposition may
   //       need to ignore selection changes caused by composition.  Therefore,
   //       CompositionChangeEventHandlingMarker must be destroyed after a call
   //       of NotifiyEditorObservers(eNotifyEditorObserversOfEnd) or
   //       NotifiyEditorObservers(eNotifyEditorObserversOfCancel) which notifies
   //       TextComposition of a selection change.
@@ -1357,17 +1431,17 @@ TextEditor::OnCompositionChange(WidgetCo
 
     MOZ_ASSERT(mIsInEditSubAction,
       "AutoPlaceholderBatch should've notified the observes of before-edit");
     rv = InsertTextAsSubAction(aCompsitionChangeEvent.mData);
     NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
       "Failed to insert new composition string");
 
     if (caretP) {
-      caretP->SetSelection(selection);
+      caretP->SetSelection(SelectionRefPtr());
     }
   }
 
   // If still composing, we should fire input event via observer.
   // Note that if the composition will be committed by the following
   // compositionend event, we don't need to notify editor observes of this
   // change.
   // NOTE: We must notify after the auto batch will be gone.
@@ -1380,16 +1454,21 @@ TextEditor::OnCompositionChange(WidgetCo
 
 void
 TextEditor::OnCompositionEnd(WidgetCompositionEvent& aCompositionEndEvent)
 {
   if (NS_WARN_IF(!mComposition)) {
     return;
   }
 
+  AutoEditActionDataSetter editActionData(*this, EditAction::eEndComposition);
+  if (NS_WARN_IF(!editActionData.CanHandle())) {
+    return;
+  }
+
   // commit the IME transaction..we can get at it via the transaction mgr.
   // Note that this means IME won't work without an undo stack!
   if (mTransactionManager) {
     nsCOMPtr<nsITransaction> txn = mTransactionManager->PeekUndoStack();
     nsCOMPtr<nsIAbsorbingTransaction> plcTxn = do_QueryInterface(txn);
     if (plcTxn) {
       DebugOnly<nsresult> rv = plcTxn->Commit();
       NS_ASSERTION(NS_SUCCEEDED(rv),
@@ -1525,16 +1604,21 @@ static void CutStyle(const char* stylena
       styleValue.Cut(styleStart, styleValue.Length() - styleStart);
     }
   }
 }
 
 NS_IMETHODIMP
 TextEditor::SetWrapWidth(int32_t aWrapColumn)
 {
+  AutoEditActionDataSetter editActionData(*this, EditAction::eSetWrapWidth);
+  if (NS_WARN_IF(!editActionData.CanHandle())) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
+
   SetWrapColumn(aWrapColumn);
 
   // Make sure we're a plaintext editor, otherwise we shouldn't
   // do the rest of this.
   if (!IsPlaintextEditor()) {
     return NS_OK;
   }
 
@@ -1618,16 +1702,21 @@ TextEditor::Undo(uint32_t aCount)
   // If there is composition, we shouldn't allow to undo with committing
   // composition since Chrome doesn't allow it and it doesn't make sense
   // because committing composition causes one transaction and Undo(1)
   // undoes the committing composition.
   if (GetComposition()) {
     return NS_OK;
   }
 
+  AutoEditActionDataSetter editActionData(*this, EditAction::eUndo);
+  if (NS_WARN_IF(!editActionData.CanHandle())) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
+
   // Protect the edit rules object from dying.
   RefPtr<TextEditRules> rules(mRules);
 
   AutoUpdateViewBatch preventSelectionChangeEvent(*this);
 
   NotifyEditorObservers(eNotifyEditorObserversOfBefore);
   if (NS_WARN_IF(!CanUndo()) || NS_WARN_IF(Destroyed())) {
     return NS_ERROR_FAILURE;
@@ -1635,29 +1724,29 @@ TextEditor::Undo(uint32_t aCount)
 
   nsresult rv;
   {
     AutoTopLevelEditSubActionNotifier maybeTopLevelEditSubAction(
                                         *this, EditSubAction::eUndo,
                                         nsIEditor::eNone);
 
     EditSubActionInfo subActionInfo(EditSubAction::eUndo);
-    RefPtr<Selection> selection = GetSelection();
     bool cancel, handled;
-    rv = rules->WillDoAction(selection, subActionInfo, &cancel, &handled);
+    rv = rules->WillDoAction(SelectionRefPtr(), subActionInfo,
+                             &cancel, &handled);
     if (!cancel && NS_SUCCEEDED(rv)) {
       RefPtr<TransactionManager> transactionManager(mTransactionManager);
       for (uint32_t i = 0; i < aCount; ++i) {
         rv = transactionManager->Undo();
         if (NS_WARN_IF(NS_FAILED(rv))) {
           break;
         }
         DoAfterUndoTransaction();
       }
-      rv = rules->DidDoAction(selection, subActionInfo, rv);
+      rv = rules->DidDoAction(SelectionRefPtr(), subActionInfo, rv);
     }
   }
 
   NotifyEditorObservers(eNotifyEditorObserversOfEnd);
   return rv;
 }
 
 NS_IMETHODIMP
@@ -1673,16 +1762,21 @@ TextEditor::Redo(uint32_t aCount)
   // If there is composition, we shouldn't allow to redo with committing
   // composition since Chrome doesn't allow it and it doesn't make sense
   // because committing composition causes removing all transactions from
   // the redo queue.  So, it becomes impossible to redo anything.
   if (GetComposition()) {
     return NS_OK;
   }
 
+  AutoEditActionDataSetter editActionData(*this, EditAction::eRedo);
+  if (NS_WARN_IF(!editActionData.CanHandle())) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
+
   // Protect the edit rules object from dying.
   RefPtr<TextEditRules> rules(mRules);
 
   AutoUpdateViewBatch preventSelectionChangeEvent(*this);
 
   NotifyEditorObservers(eNotifyEditorObserversOfBefore);
   if (NS_WARN_IF(!CanRedo()) || NS_WARN_IF(Destroyed())) {
     return NS_ERROR_FAILURE;
@@ -1690,29 +1784,29 @@ TextEditor::Redo(uint32_t aCount)
 
   nsresult rv;
   {
     AutoTopLevelEditSubActionNotifier maybeTopLevelEditSubAction(
                                         *this, EditSubAction::eRedo,
                                         nsIEditor::eNone);
 
     EditSubActionInfo subActionInfo(EditSubAction::eRedo);
-    RefPtr<Selection> selection = GetSelection();
     bool cancel, handled;
-    rv = rules->WillDoAction(selection, subActionInfo, &cancel, &handled);
+    rv = rules->WillDoAction(SelectionRefPtr(), subActionInfo,
+                             &cancel, &handled);
     if (!cancel && NS_SUCCEEDED(rv)) {
       RefPtr<TransactionManager> transactionManager(mTransactionManager);
       for (uint32_t i = 0; i < aCount; ++i) {
         nsresult rv = transactionManager->Redo();
         if (NS_WARN_IF(NS_FAILED(rv))) {
           break;
         }
         DoAfterRedoTransaction();
       }
-      rv = rules->DidDoAction(selection, subActionInfo, rv);
+      rv = rules->DidDoAction(SelectionRefPtr(), subActionInfo, rv);
     }
   }
 
   NotifyEditorObservers(eNotifyEditorObserversOfEnd);
   return rv;
 }
 
 bool
@@ -1756,62 +1850,96 @@ TextEditor::FireClipboardEvent(EventMess
   // If the event handler caused the editor to be destroyed, return false.
   // Otherwise return true to indicate that the event was not cancelled.
   return !mDidPreDestroy;
 }
 
 NS_IMETHODIMP
 TextEditor::Cut()
 {
+  AutoEditActionDataSetter editActionData(*this, EditAction::eCut);
+  if (NS_WARN_IF(!editActionData.CanHandle())) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
+
   bool actionTaken = false;
   if (FireClipboardEvent(eCut, nsIClipboard::kGlobalClipboard, &actionTaken)) {
     // XXX This transaction name is referred by PlaceholderTransaction::Merge()
     //     so that we need to keep using it here.
     AutoPlaceholderBatch treatAsOneTransaction(*this,
                                                *nsGkAtoms::DeleteTxnName);
     DeleteSelectionAsSubAction(eNone, eStrip);
   }
   return actionTaken ? NS_OK : NS_ERROR_FAILURE;
 }
 
 NS_IMETHODIMP
 TextEditor::CanCut(bool* aCanCut)
 {
-  NS_ENSURE_ARG_POINTER(aCanCut);
+  if (NS_WARN_IF(!aCanCut)) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing);
+  if (NS_WARN_IF(!editActionData.CanHandle())) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
+
   // Cut is always enabled in HTML documents
   nsCOMPtr<nsIDocument> doc = GetDocument();
   *aCanCut = (doc && doc->IsHTMLOrXHTML()) ||
     (IsModifiable() && CanCutOrCopy(ePasswordFieldNotAllowed));
   return NS_OK;
 }
 
 NS_IMETHODIMP
 TextEditor::Copy()
 {
+  AutoEditActionDataSetter editActionData(*this, EditAction::eCopy);
+  if (NS_WARN_IF(!editActionData.CanHandle())) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
+
   bool actionTaken = false;
   FireClipboardEvent(eCopy, nsIClipboard::kGlobalClipboard, &actionTaken);
 
   return actionTaken ? NS_OK : NS_ERROR_FAILURE;
 }
 
 NS_IMETHODIMP
 TextEditor::CanCopy(bool* aCanCopy)
 {
-  NS_ENSURE_ARG_POINTER(aCanCopy);
+  if (NS_WARN_IF(!aCanCopy)) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing);
+  if (NS_WARN_IF(!editActionData.CanHandle())) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
+
   // Copy is always enabled in HTML documents
   nsCOMPtr<nsIDocument> doc = GetDocument();
   *aCanCopy = (doc && doc->IsHTMLOrXHTML()) ||
     CanCutOrCopy(ePasswordFieldNotAllowed);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 TextEditor::CanDelete(bool* aCanDelete)
 {
-  NS_ENSURE_ARG_POINTER(aCanDelete);
+  if (NS_WARN_IF(!aCanDelete)) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing);
+  if (NS_WARN_IF(!editActionData.CanHandle())) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
+
   *aCanDelete = IsModifiable() && CanCutOrCopy(ePasswordFieldAllowed);
   return NS_OK;
 }
 
 already_AddRefed<nsIDocumentEncoder>
 TextEditor::GetAndInitDocEncoder(const nsAString& aFormatType,
                                  uint32_t aDocumentEncoderFlags,
                                  const nsACString& aCharset) const
@@ -1883,16 +2011,21 @@ TextEditor::GetAndInitDocEncoder(const n
   return docEncoder.forget();
 }
 
 NS_IMETHODIMP
 TextEditor::OutputToString(const nsAString& aFormatType,
                            uint32_t aDocumentEncoderFlags,
                            nsAString& aOutputString)
 {
+  AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing);
+  if (NS_WARN_IF(!editActionData.CanHandle())) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
+
   return ComputeValueInternal(aFormatType, aDocumentEncoderFlags,
                               aOutputString);
 }
 
 nsresult
 TextEditor::ComputeValueInternal(const nsAString& aFormatType,
                                  uint32_t aDocumentEncoderFlags,
                                  nsAString& aOutputString) const
@@ -1937,16 +2070,21 @@ TextEditor::ComputeValueInternal(const n
 
 nsresult
 TextEditor::PasteAsQuotationAsAction(int32_t aClipboardType,
                                      bool aDispatchPasteEvent)
 {
   MOZ_ASSERT(aClipboardType == nsIClipboard::kGlobalClipboard ||
              aClipboardType == nsIClipboard::kSelectionClipboard);
 
+  AutoEditActionDataSetter editActionData(*this, EditAction::ePaste);
+  if (NS_WARN_IF(!editActionData.CanHandle())) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
+
   // Get Clipboard Service
   nsresult rv;
   nsCOMPtr<nsIClipboard> clipboard =
     do_GetService("@mozilla.org/widget/clipboard;1", &rv);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
@@ -2157,43 +2295,56 @@ nsresult
 TextEditor::SetAttributeOrEquivalent(Element* aElement,
                                      nsAtom* aAttribute,
                                      const nsAString& aValue,
                                      bool aSuppressTransaction)
 {
   if (NS_WARN_IF(!aElement) || NS_WARN_IF(!aAttribute)) {
     return NS_ERROR_INVALID_ARG;
   }
+
+  AutoEditActionDataSetter editActionData(*this, EditAction::eSetAttribute);
+  if (NS_WARN_IF(!editActionData.CanHandle())) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
+
   return SetAttributeWithTransaction(*aElement, *aAttribute, aValue);
 }
 
 nsresult
 TextEditor::RemoveAttributeOrEquivalent(Element* aElement,
                                         nsAtom* aAttribute,
                                         bool aSuppressTransaction)
 {
   if (NS_WARN_IF(!aElement) || NS_WARN_IF(!aAttribute)) {
     return NS_ERROR_INVALID_ARG;
   }
+
+  AutoEditActionDataSetter editActionData(*this, EditAction::eRemoveAttribute);
+  if (NS_WARN_IF(!editActionData.CanHandle())) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
+
   return RemoveAttributeWithTransaction(*aElement, *aAttribute);
 }
 
 nsresult
 TextEditor::HideLastPasswordInput()
 {
   // This method should be called only by TextEditRules::Notify().
   MOZ_ASSERT(mRules);
   MOZ_ASSERT(IsPasswordEditor());
+  MOZ_ASSERT(!IsEditActionDataAvailable());
 
-  RefPtr<Selection> selection = GetSelection();
-  if (NS_WARN_IF(!selection)) {
-    return NS_ERROR_FAILURE;
+  AutoEditActionDataSetter editActionData(*this, EditAction::eHidePassword);
+  if (NS_WARN_IF(!editActionData.CanHandle())) {
+    return NS_ERROR_NOT_INITIALIZED;
   }
 
   RefPtr<TextEditRules> rules(mRules);
-  nsresult rv = rules->HideLastPasswordInput(*selection);
+  nsresult rv = rules->HideLastPasswordInput(*SelectionRefPtr());
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
   return NS_OK;
 }
 
 } // namespace mozilla
--- a/editor/libeditor/TextEditor.h
+++ b/editor/libeditor/TextEditor.h
@@ -227,16 +227,20 @@ public:
    * too expensive if it's in hot path.
    *
    * @param aDocumentEncoderFlags   Flags of nsIDocumentEncoder.
    * @param aCharset                Encoding of the document.
    */
   nsresult ComputeTextValue(uint32_t aDocumentEncoderFlags,
                             nsAString& aOutputString) const
   {
+    AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing);
+    if (NS_WARN_IF(!editActionData.CanHandle())) {
+      return NS_ERROR_NOT_INITIALIZED;
+    }
     return ComputeValueInternal(NS_LITERAL_STRING("text/plain"),
                                 aDocumentEncoderFlags, aOutputString);
   }
 
 protected: // May be called by friends.
   /****************************************************************************
    * Some classes like TextEditRules, HTMLEditRules, WSRunObject which are
    * part of handling edit actions are allowed to call the following protected
@@ -335,17 +339,17 @@ protected: // May be called by friends.
    */
   nsresult ExtendSelectionForDelete(Selection* aSelection,
                                     nsIEditor::EDirection* aAction);
 
   /**
    * HideLastPasswordInput() is called by timer callback of TextEditRules.
    * This should be called only by TextEditRules::Notify().
    * When this is called, the TextEditRules wants to call its
-   * HideLastPasswordInput().
+   * HideLastPasswordInput() with AutoEditActionDataSetter instance.
    */
   MOZ_CAN_RUN_SCRIPT_BOUNDARY nsresult HideLastPasswordInput();
 
   static void GetDefaultEditorPrefs(int32_t& aNewLineHandling,
                                     int32_t& aCaretStyle);
 
 protected: // Called by helper classes.
 
--- a/editor/libeditor/TextEditorDataTransfer.cpp
+++ b/editor/libeditor/TextEditorDataTransfer.cpp
@@ -159,19 +159,26 @@ TextEditor::InsertFromDataTransfer(DataT
   }
 
   return NS_OK;
 }
 
 nsresult
 TextEditor::OnDrop(DragEvent* aDropEvent)
 {
+  if (NS_WARN_IF(!aDropEvent)) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
   CommitComposition();
 
-  NS_ENSURE_TRUE(aDropEvent, NS_ERROR_FAILURE);
+  AutoEditActionDataSetter editActionData(*this, EditAction::eDrop);
+  if (NS_WARN_IF(!editActionData.CanHandle())) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
 
   RefPtr<DataTransfer> dataTransfer = aDropEvent->GetDataTransfer();
   NS_ENSURE_TRUE(dataTransfer, NS_ERROR_FAILURE);
 
   nsCOMPtr<nsIDragSession> dragSession = nsContentUtils::GetDragSession();
   NS_ASSERTION(dragSession, "No drag session");
 
   nsCOMPtr<nsINode> sourceNode = dataTransfer->GetMozSourceNode();
@@ -208,32 +215,27 @@ TextEditor::OnDrop(DragEvent* aDropEvent
 
   // We have to figure out whether to delete and relocate caret only once
   // Parent and offset are under the mouse cursor
   nsCOMPtr<nsINode> newSelectionParent = aDropEvent->GetRangeParent();
   NS_ENSURE_TRUE(newSelectionParent, NS_ERROR_FAILURE);
 
   int32_t newSelectionOffset = aDropEvent->RangeOffset();
 
-  RefPtr<Selection> selection = GetSelection();
-  NS_ENSURE_TRUE(selection, NS_ERROR_FAILURE);
-
-  bool isCollapsed = selection->IsCollapsed();
-
   // Check if mouse is in the selection
   // if so, jump through some hoops to determine if mouse is over selection (bail)
   // and whether user wants to copy selection or delete it
-  if (!isCollapsed) {
+  if (!SelectionRefPtr()->IsCollapsed()) {
     // We never have to delete if selection is already collapsed
     bool cursorIsInSelection = false;
 
-    uint32_t rangeCount = selection->RangeCount();
+    uint32_t rangeCount = SelectionRefPtr()->RangeCount();
 
     for (uint32_t j = 0; j < rangeCount; j++) {
-      RefPtr<nsRange> range = selection->GetRangeAt(j);
+      RefPtr<nsRange> range = SelectionRefPtr()->GetRangeAt(j);
       if (!range) {
         // don't bail yet, iterate through them all
         continue;
       }
 
       IgnoredErrorResult rv;
       cursorIsInSelection =
         range->IsPointInRange(*newSelectionParent, newSelectionOffset, rv);
@@ -292,16 +294,21 @@ TextEditor::OnDrop(DragEvent* aDropEvent
 
   return NS_OK;
 }
 
 nsresult
 TextEditor::PasteAsAction(int32_t aClipboardType,
                           bool aDispatchPasteEvent)
 {
+  AutoEditActionDataSetter editActionData(*this, EditAction::ePaste);
+  if (NS_WARN_IF(!editActionData.CanHandle())) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
+
   if (AsHTMLEditor()) {
     nsresult rv =
       AsHTMLEditor()->PasteInternal(aClipboardType, aDispatchPasteEvent);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
     return NS_OK;
   }
@@ -341,16 +348,21 @@ TextEditor::PasteAsAction(int32_t aClipb
     return rv;
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
 TextEditor::PasteTransferable(nsITransferable* aTransferable)
 {
+  AutoEditActionDataSetter editActionData(*this, EditAction::ePaste);
+  if (NS_WARN_IF(!editActionData.CanHandle())) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
+
   // Use an invalid value for the clipboard type as data comes from aTransferable
   // and we don't currently implement a way to put that in the data transfer yet.
   if (!FireClipboardEvent(ePaste, -1)) {
     return NS_OK;
   }
 
   if (!IsModifiable()) {
     return NS_OK;