Bug 1451672 - part 9: Create TextEditor::DeleteSelectionAsAction() as implementation of nsIEditor::DeleteSelection() r=m_kato
authorMasayuki Nakano <masayuki@d-toybox.com>
Wed, 11 Apr 2018 17:37:49 +0900
changeset 468362 585bed4748a744017ef8cdb2cdbfea922c3e67b2
parent 468361 5e8f25ef1174d4af6ac4c16c02effa8c49c432ed
child 468363 81798a8610e1b350998ea29ef177fcd5a8c0b801
push id9165
push userasasaki@mozilla.com
push dateThu, 26 Apr 2018 21:04:54 +0000
treeherdermozilla-beta@064c3804de2e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersm_kato
bugs1451672
milestone61.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 1451672 - part 9: Create TextEditor::DeleteSelectionAsAction() as implementation of nsIEditor::DeleteSelection() r=m_kato First, EditorBase::DeleteSelection() is never used since TextEditor::DeleteSelection() overrides it but does not call it. So, this patch makes EditorBase::DeleteSelection() only returns NS_ERROR_NOT_IMPLEMENTED. Next, EditorBase::DeleteSelectionImpl() actually removes content for TextEditor::DeleteSelection(). So, it should be named as DeleteSelectionWithTransaction(). However, it'll be done in the following patch. On the other hand, its callers are EditorBase::HandleKeyPressEvent() and EditorBase::DeleteSelectionAndPrepareToCreateNode(). Fortunately, they can be moved to TextEditor simply. Therefore this patch moves the methods to TextEditor for making related methods in a place. Then, we can make the implementation of nsIEditor::TextEditor() as a non-virtual method, TextEditor::DeleteSelectionAsAction(). MozReview-Commit-ID: KXFDhW3G9lA
accessible/generic/HyperTextAccessible-inl.h
dom/html/nsTextEditorState.cpp
editor/libeditor/EditorBase.cpp
editor/libeditor/EditorBase.h
editor/libeditor/EditorCommands.cpp
editor/libeditor/HTMLEditRules.cpp
editor/libeditor/HTMLEditor.cpp
editor/libeditor/HTMLEditor.h
editor/libeditor/HTMLEditorDataTransfer.cpp
editor/libeditor/HTMLTableEditor.cpp
editor/libeditor/TextEditRules.cpp
editor/libeditor/TextEditor.cpp
editor/libeditor/TextEditor.h
editor/libeditor/TextEditorDataTransfer.cpp
editor/spellchecker/TextServicesDocument.cpp
--- a/accessible/generic/HyperTextAccessible-inl.h
+++ b/accessible/generic/HyperTextAccessible-inl.h
@@ -102,20 +102,23 @@ HyperTextAccessible::CutText(int32_t aSt
       textEditor->Cut();
     }
   }
 
 inline void
 HyperTextAccessible::DeleteText(int32_t aStartPos, int32_t aEndPos)
 {
   RefPtr<TextEditor> textEditor = GetEditor();
-  if (textEditor) {
-    SetSelectionRange(aStartPos, aEndPos);
-    textEditor->DeleteSelection(nsIEditor::eNone, nsIEditor::eStrip);
+  if (!textEditor) {
+    return;
   }
+  SetSelectionRange(aStartPos, aEndPos);
+  DebugOnly<nsresult> rv =
+    textEditor->DeleteSelectionAsAction(nsIEditor::eNone, nsIEditor::eStrip);
+  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Failed to delete text");
 }
 
 inline void
 HyperTextAccessible::PasteText(int32_t aPosition)
 {
   RefPtr<TextEditor> textEditor = GetEditor();
   if (textEditor) {
     SetSelectionRange(aPosition, aPosition);
--- a/dom/html/nsTextEditorState.cpp
+++ b/dom/html/nsTextEditorState.cpp
@@ -2417,17 +2417,21 @@ nsTextEditorState::SetValue(const nsAStr
             } else {
               // Collapse selection to the end so that we can append data.
               mBoundFrame->SelectAllOrCollapseToEndOfText(false);
             }
             const nsAString& insertValue =
               StringTail(newValue, newlength - currentLength);
 
             if (insertValue.IsEmpty()) {
-              textEditor->DeleteSelection(nsIEditor::eNone, nsIEditor::eStrip);
+              DebugOnly<nsresult> rv =
+                textEditor->DeleteSelectionAsAction(nsIEditor::eNone,
+                                                    nsIEditor::eStrip);
+              NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+                "Failed to remove the text");
             } else {
               textEditor->InsertText(insertValue);
             }
           } else {
             AutoDisableUndo disableUndo(textEditor);
             if (selection) {
               // Since we don't use undo transaction, we don't need to store
               // selection state.  SetText will set selection to tail.
--- a/editor/libeditor/EditorBase.cpp
+++ b/editor/libeditor/EditorBase.cpp
@@ -702,18 +702,17 @@ EditorBase::GetSelectionController(nsISe
   selCon.forget(aSel);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 EditorBase::DeleteSelection(EDirection aAction,
                             EStripWrappers aStripWrappers)
 {
-  MOZ_ASSERT(aStripWrappers == eStrip || aStripWrappers == eNoStrip);
-  return DeleteSelectionImpl(aAction, aStripWrappers);
+  return NS_ERROR_NOT_IMPLEMENTED;
 }
 
 NS_IMETHODIMP
 EditorBase::GetSelection(nsISelection** aSelection)
 {
   return GetSelection(SelectionType::eNormal, aSelection);
 }
 
@@ -2488,30 +2487,30 @@ EditorBase::GetRootElement(nsIDOMElement
   rootElement.forget(aRootElement);
   return NS_OK;
 }
 
 /**
  * All editor operations which alter the doc should be prefaced
  * with a call to StartOperation, naming the action and direction.
  */
-NS_IMETHODIMP
+nsresult
 EditorBase::StartOperation(EditAction opID,
                            nsIEditor::EDirection aDirection)
 {
   mAction = opID;
   mDirection = aDirection;
   return NS_OK;
 }
 
 /**
  * All editor operations which alter the doc should be followed
  * with a call to EndOperation.
  */
-NS_IMETHODIMP
+nsresult
 EditorBase::EndOperation()
 {
   mAction = EditAction::none;
   mDirection = eNone;
   return NS_OK;
 }
 
 NS_IMETHODIMP
@@ -4257,136 +4256,16 @@ EditorBase::EndUpdateViewBatch()
 }
 
 bool
 EditorBase::GetShouldTxnSetSelection()
 {
   return mShouldTxnSetSelection;
 }
 
-NS_IMETHODIMP
-EditorBase::DeleteSelectionImpl(EDirection aAction,
-                                EStripWrappers aStripWrappers)
-{
-  MOZ_ASSERT(aStripWrappers == eStrip || aStripWrappers == eNoStrip);
-
-  RefPtr<Selection> selection = GetSelection();
-  NS_ENSURE_STATE(selection);
-
-  RefPtr<EditAggregateTransaction> deleteSelectionTransaction;
-  nsCOMPtr<nsINode> deleteNode;
-  int32_t deleteCharOffset = 0, deleteCharLength = 0;
-  if (!selection->Collapsed() || aAction != eNone) {
-    deleteSelectionTransaction =
-      CreateTxnForDeleteSelection(aAction,
-                                  getter_AddRefs(deleteNode),
-                                  &deleteCharOffset,
-                                  &deleteCharLength);
-    if (NS_WARN_IF(!deleteSelectionTransaction)) {
-      return NS_ERROR_FAILURE;
-    }
-  }
-
-  RefPtr<CharacterData> deleteCharData =
-    CharacterData::FromNodeOrNull(deleteNode);
-  AutoRules beginRulesSniffing(this, EditAction::deleteSelection, aAction);
-
-  if (mRules && mRules->AsHTMLEditRules()) {
-    if (!deleteNode) {
-      RefPtr<HTMLEditRules> htmlEditRules = mRules->AsHTMLEditRules();
-      htmlEditRules->WillDeleteSelection(selection);
-    } else if (!deleteCharData) {
-      RefPtr<HTMLEditRules> htmlEditRules = mRules->AsHTMLEditRules();
-      htmlEditRules->WillDeleteNode(deleteNode);
-    }
-  }
-
-  // Notify nsIEditActionListener::WillDelete[Selection|Text]
-  if (!mActionListeners.IsEmpty()) {
-    if (!deleteNode) {
-      AutoActionListenerArray listeners(mActionListeners);
-      for (auto& listener : listeners) {
-        listener->WillDeleteSelection(selection);
-      }
-    } else if (deleteCharData) {
-      AutoActionListenerArray listeners(mActionListeners);
-      for (auto& listener : listeners) {
-        listener->WillDeleteText(deleteCharData, deleteCharOffset, 1);
-      }
-    }
-  }
-
-  // Delete the specified amount
-  nsresult rv = DoTransaction(deleteSelectionTransaction);
-
-  if (mRules && mRules->AsHTMLEditRules() && deleteCharData) {
-    RefPtr<HTMLEditRules> htmlEditRules = mRules->AsHTMLEditRules();
-    htmlEditRules->DidDeleteText(deleteNode, deleteCharOffset, 1);
-  }
-
-  if (mTextServicesDocument && NS_SUCCEEDED(rv) &&
-      deleteNode && !deleteCharData) {
-    RefPtr<TextServicesDocument> textServicesDocument = mTextServicesDocument;
-    textServicesDocument->DidDeleteNode(deleteNode);
-  }
-
-  // Notify nsIEditActionListener::DidDelete[Selection|Text|Node]
-  {
-    AutoActionListenerArray listeners(mActionListeners);
-    if (!deleteNode) {
-      for (auto& listener : mActionListeners) {
-        listener->DidDeleteSelection(selection);
-      }
-    } else if (deleteCharData) {
-      for (auto& listener : mActionListeners) {
-        listener->DidDeleteText(deleteCharData, deleteCharOffset, 1, rv);
-      }
-    } else {
-      for (auto& listener : mActionListeners) {
-        listener->DidDeleteNode(deleteNode->AsDOMNode(), rv);
-      }
-    }
-  }
-
-  return rv;
-}
-
-already_AddRefed<Element>
-EditorBase::DeleteSelectionAndCreateElement(nsAtom& aTag)
-{
-  nsresult rv = DeleteSelectionAndPrepareToCreateNode();
-  NS_ENSURE_SUCCESS(rv, nullptr);
-
-  RefPtr<Selection> selection = GetSelection();
-  NS_ENSURE_TRUE(selection, nullptr);
-
-  EditorRawDOMPoint pointToInsert(selection->AnchorRef());
-  if (!pointToInsert.IsSet()) {
-    return nullptr;
-  }
-  RefPtr<Element> newElement = CreateNodeWithTransaction(aTag, pointToInsert);
-
-  // We want the selection to be just after the new node
-  EditorRawDOMPoint afterNewElement(newElement);
-  MOZ_ASSERT(afterNewElement.IsSetAndValid());
-  DebugOnly<bool> advanced = afterNewElement.AdvanceOffset();
-  NS_WARNING_ASSERTION(advanced,
-                       "Failed to move offset next to the new element");
-  ErrorResult error;
-  selection->Collapse(afterNewElement, error);
-  if (NS_WARN_IF(error.Failed())) {
-    // XXX Even if it succeeded to create new element, this returns error
-    //     when Selection.Collapse() fails something.  This could occur with
-    //     mutation observer or mutation event listener.
-    error.SuppressException();
-    return nullptr;
-  }
-  return newElement.forget();
-}
-
 TextComposition*
 EditorBase::GetComposition() const
 {
   return mComposition;
 }
 
 bool
 EditorBase::IsIMEComposing() const
@@ -4398,89 +4277,16 @@ bool
 EditorBase::ShouldHandleIMEComposition() const
 {
   // When the editor is being reframed, the old value may be restored with
   // InsertText().  In this time, the text should be inserted as not a part
   // of the composition.
   return mComposition && mDidPostCreate;
 }
 
-nsresult
-EditorBase::DeleteSelectionAndPrepareToCreateNode()
-{
-  RefPtr<Selection> selection = GetSelection();
-  NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
-  if (NS_WARN_IF(!selection->GetAnchorFocusRange())) {
-    return NS_OK;
-  }
-
-  if (!selection->GetAnchorFocusRange()->Collapsed()) {
-    nsresult rv = DeleteSelection(nsIEditor::eNone, nsIEditor::eStrip);
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    MOZ_ASSERT(selection->GetAnchorFocusRange() &&
-               selection->GetAnchorFocusRange()->Collapsed(),
-               "Selection not collapsed after delete");
-  }
-
-  // If the selection is a chardata node, split it if necessary and compute
-  // where to put the new node
-  EditorDOMPoint atAnchor(selection->AnchorRef());
-  if (NS_WARN_IF(!atAnchor.IsSet()) || !atAnchor.IsInDataNode()) {
-    return NS_OK;
-  }
-
-  if (NS_WARN_IF(!atAnchor.GetContainer()->GetParentNode())) {
-    return NS_ERROR_FAILURE;
-  }
-
-  if (atAnchor.IsStartOfContainer()) {
-    EditorRawDOMPoint atAnchorContainer(atAnchor.GetContainer());
-    if (NS_WARN_IF(!atAnchorContainer.IsSetAndValid())) {
-      return NS_ERROR_FAILURE;
-    }
-    ErrorResult error;
-    selection->Collapse(atAnchorContainer, error);
-    if (NS_WARN_IF(error.Failed())) {
-      return error.StealNSResult();
-    }
-    return NS_OK;
-  }
-
-  if (atAnchor.IsEndOfContainer()) {
-    EditorRawDOMPoint afterAnchorContainer(atAnchor.GetContainer());
-    if (NS_WARN_IF(!afterAnchorContainer.AdvanceOffset())) {
-      return NS_ERROR_FAILURE;
-    }
-    ErrorResult error;
-    selection->Collapse(afterAnchorContainer, error);
-    if (NS_WARN_IF(error.Failed())) {
-      return error.StealNSResult();
-    }
-    return NS_OK;
-  }
-
-  ErrorResult error;
-  nsCOMPtr<nsIContent> newLeftNode = SplitNodeWithTransaction(atAnchor, error);
-  if (NS_WARN_IF(error.Failed())) {
-    return error.StealNSResult();
-  }
-
-  EditorRawDOMPoint atRightNode(atAnchor.GetContainer());
-  if (NS_WARN_IF(!atRightNode.IsSet())) {
-    return NS_ERROR_FAILURE;
-  }
-  MOZ_ASSERT(atRightNode.IsSetAndValid());
-  selection->Collapse(atRightNode, error);
-  if (NS_WARN_IF(error.Failed())) {
-    return error.StealNSResult();
-  }
-  return NS_OK;
-}
-
 void
 EditorBase::DoAfterDoTransaction(nsITransaction* aTxn)
 {
   bool isTransientTransaction;
   MOZ_ALWAYS_SUCCEEDS(aTxn->GetIsTransient(&isTransientTransaction));
 
   if (!isTransientTransaction) {
     // we need to deal here with the case where the user saved after some
@@ -4892,36 +4698,16 @@ EditorBase::HandleKeyPressEvent(WidgetKe
   switch (aKeyboardEvent->mKeyCode) {
     case NS_VK_META:
     case NS_VK_WIN:
     case NS_VK_SHIFT:
     case NS_VK_CONTROL:
     case NS_VK_ALT:
       aKeyboardEvent->PreventDefault(); // consumed
       return NS_OK;
-    case NS_VK_BACK:
-      if (aKeyboardEvent->IsControl() || aKeyboardEvent->IsAlt() ||
-          aKeyboardEvent->IsMeta() || aKeyboardEvent->IsOS()) {
-        return NS_OK;
-      }
-      DeleteSelection(nsIEditor::ePrevious, nsIEditor::eStrip);
-      aKeyboardEvent->PreventDefault(); // consumed
-      return NS_OK;
-    case NS_VK_DELETE:
-      // on certain platforms (such as windows) the shift key
-      // modifies what delete does (cmd_cut in this case).
-      // bailing here to allow the keybindings to do the cut.
-      if (aKeyboardEvent->IsShift() || aKeyboardEvent->IsControl() ||
-          aKeyboardEvent->IsAlt() || aKeyboardEvent->IsMeta() ||
-          aKeyboardEvent->IsOS()) {
-        return NS_OK;
-      }
-      DeleteSelection(nsIEditor::eNext, nsIEditor::eStrip);
-      aKeyboardEvent->PreventDefault(); // consumed
-      return NS_OK;
   }
   return NS_OK;
 }
 
 nsresult
 EditorBase::HandleInlineSpellCheck(EditAction action,
                                    Selection* aSelection,
                                    nsINode* previousSelectedNode,
--- a/editor/libeditor/EditorBase.h
+++ b/editor/libeditor/EditorBase.h
@@ -324,21 +324,16 @@ public:
   nsresult InsertTextIntoTextNodeImpl(const nsAString& aStringToInsert,
                                       Text& aTextNode, int32_t aOffset,
                                       bool aSuppressIME = false);
 
   nsresult SetTextImpl(Selection& aSelection,
                        const nsAString& aString,
                        Text& aTextNode);
 
-  NS_IMETHOD DeleteSelectionImpl(EDirection aAction,
-                                 EStripWrappers aStripWrappers);
-
-  already_AddRefed<Element> DeleteSelectionAndCreateElement(nsAtom& aTag);
-
   /**
    * DeleteNodeWithTransaction() removes aNode from the DOM tree.
    *
    * @param aNode       The node which will be removed form the DOM tree.
    */
   nsresult DeleteNodeWithTransaction(nsINode& aNode);
 
   /**
@@ -586,25 +581,16 @@ protected:
    * @param aCharData           The data node which should be modified.
    * @param aOffset             Start offset of removing text in aCharData.
    * @param aLength             Length of removing text.
    */
   nsresult DeleteTextWithTransaction(dom::CharacterData& aCharacterData,
                                      uint32_t aOffset, uint32_t aLength);
 
   /**
-   * This method first deletes the selection, if it's not collapsed.  Then if
-   * the selection lies in a CharacterData node, it splits it.  If the
-   * selection is at this point collapsed in a CharacterData node, it's
-   * adjusted to be collapsed right before or after the node instead (which is
-   * always possible, since the node was split).
-   */
-  nsresult DeleteSelectionAndPrepareToCreateNode();
-
-  /**
    * Called after a transaction is done successfully.
    */
   void DoAfterDoTransaction(nsITransaction *aTxn);
 
   /**
    * Called after a transaction is undone successfully.
    */
 
@@ -775,24 +761,24 @@ protected:
   virtual void InitializeSelectionAncestorLimit(Selection& aSelection,
                                                 nsIContent& aAncestorLimit);
 
 public:
   /**
    * All editor operations which alter the doc should be prefaced
    * with a call to StartOperation, naming the action and direction.
    */
-  NS_IMETHOD StartOperation(EditAction opID,
-                            nsIEditor::EDirection aDirection);
+  virtual nsresult StartOperation(EditAction opID,
+                                  nsIEditor::EDirection aDirection);
 
   /**
    * All editor operations which alter the doc should be followed
    * with a call to EndOperation.
    */
-  NS_IMETHOD EndOperation();
+  virtual nsresult EndOperation();
 
   /**
    * Routines for managing the preservation of selection across
    * various editor actions.
    */
   bool ArePreservingSelection();
   void PreserveSelectionAcrossActions(Selection* aSel);
   nsresult RestorePreservedSelection(Selection* aSel);
--- a/editor/libeditor/EditorCommands.cpp
+++ b/editor/libeditor/EditorCommands.cpp
@@ -311,17 +311,23 @@ CutOrDeleteCommand::DoCommand(const char
   nsCOMPtr<nsIEditor> editor = do_QueryInterface(aCommandRefCon);
   if (!editor) {
     return NS_ERROR_FAILURE;
   }
   TextEditor* textEditor = editor->AsTextEditor();
   MOZ_ASSERT(textEditor);
   dom::Selection* selection = textEditor->GetSelection();
   if (selection && selection->Collapsed()) {
-    return textEditor->DeleteSelection(nsIEditor::eNext, nsIEditor::eStrip);
+    nsresult rv =
+      textEditor->DeleteSelectionAsAction(nsIEditor::eNext,
+                                          nsIEditor::eStrip);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+    return NS_OK;
   }
   return textEditor->Cut();
 }
 
 NS_IMETHODIMP
 CutOrDeleteCommand::DoCommandParams(const char* aCommandName,
                                     nsICommandParams* aParams,
                                     nsISupports* aCommandRefCon)
@@ -423,17 +429,23 @@ CopyOrDeleteCommand::DoCommand(const cha
   nsCOMPtr<nsIEditor> editor = do_QueryInterface(aCommandRefCon);
   if (!editor) {
     return NS_ERROR_FAILURE;
   }
   TextEditor* textEditor = editor->AsTextEditor();
   MOZ_ASSERT(textEditor);
   dom::Selection* selection = textEditor->GetSelection();
   if (selection && selection->Collapsed()) {
-    return textEditor->DeleteSelection(nsIEditor::eNextWord, nsIEditor::eStrip);
+    nsresult rv =
+      textEditor->DeleteSelectionAsAction(nsIEditor::eNextWord,
+                                          nsIEditor::eStrip);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+    return NS_OK;
   }
   return textEditor->Copy();
 }
 
 NS_IMETHODIMP
 CopyOrDeleteCommand::DoCommandParams(const char* aCommandName,
                                      nsICommandParams* aParams,
                                      nsISupports* aCommandRefCon)
@@ -784,17 +796,22 @@ DeleteCommand::DoCommand(const char* aCo
   } else if (!nsCRT::strcmp("cmd_deleteToEndOfLine", aCommandName)) {
     deleteDir = nsIEditor::eToEndOfLine;
   } else {
     MOZ_CRASH("Unrecognized nsDeleteCommand");
   }
 
   TextEditor* textEditor = editor->AsTextEditor();
   MOZ_ASSERT(textEditor);
-  return textEditor->DeleteSelection(deleteDir, nsIEditor::eStrip);
+  nsresult rv =
+    textEditor->DeleteSelectionAsAction(deleteDir, nsIEditor::eStrip);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+  return NS_OK;
 }
 
 NS_IMETHODIMP
 DeleteCommand::DoCommandParams(const char* aCommandName,
                                nsICommandParams* aParams,
                                nsISupports* aCommandRefCon)
 {
   return DoCommand(aCommandName, aCommandRefCon);
--- a/editor/libeditor/HTMLEditRules.cpp
+++ b/editor/libeditor/HTMLEditRules.cpp
@@ -1283,88 +1283,97 @@ nsresult
 HTMLEditRules::WillInsertText(EditAction aAction,
                               Selection* aSelection,
                               bool* aCancel,
                               bool* aHandled,
                               const nsAString* inString,
                               nsAString* outString,
                               int32_t aMaxLength)
 {
-  if (!aSelection || !aCancel || !aHandled) {
+  if (NS_WARN_IF(!aSelection) ||
+      NS_WARN_IF(!aCancel) ||
+      NS_WARN_IF(!aHandled)) {
     return NS_ERROR_NULL_POINTER;
   }
 
+  if (NS_WARN_IF(!mHTMLEditor)) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+  RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);
+
   // initialize out param
   *aCancel = false;
   *aHandled = true;
   // If the selection isn't collapsed, delete it.  Don't delete existing inline
   // tags, because we're hopefully going to insert text (bug 787432).
   if (!aSelection->Collapsed()) {
-    NS_ENSURE_STATE(mHTMLEditor);
     nsresult rv =
-      mHTMLEditor->DeleteSelection(nsIEditor::eNone, nsIEditor::eNoStrip);
-    NS_ENSURE_SUCCESS(rv, rv);
+      htmlEditor->DeleteSelectionAsAction(nsIEditor::eNone,
+                                          nsIEditor::eNoStrip);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
   }
 
   WillInsert(*aSelection, aCancel);
   // initialize out param
   // we want to ignore result of WillInsert()
   *aCancel = false;
 
   // we need to get the doc
-  NS_ENSURE_STATE(mHTMLEditor);
-  nsCOMPtr<nsIDocument> doc = mHTMLEditor->GetDocument();
-  NS_ENSURE_STATE(doc);
+  nsCOMPtr<nsIDocument> doc = htmlEditor->GetDocument();
+  if (NS_WARN_IF(!doc)) {
+    return NS_ERROR_FAILURE;
+  }
 
   // for every property that is set, insert a new inline style node
   nsresult rv = CreateStyleForInsertText(*aSelection, *doc);
-  NS_ENSURE_SUCCESS(rv, rv);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
 
   // get the (collapsed) selection location
-  NS_ENSURE_STATE(mHTMLEditor);
   nsRange* firstRange = aSelection->GetRangeAt(0);
   if (NS_WARN_IF(!firstRange)) {
     return NS_ERROR_FAILURE;
   }
   EditorDOMPoint pointToInsert(firstRange->StartRef());
   if (NS_WARN_IF(!pointToInsert.IsSet())) {
     return NS_ERROR_FAILURE;
   }
   MOZ_ASSERT(pointToInsert.IsSetAndValid());
 
   // dont put text in places that can't have it
-  if (NS_WARN_IF(!mHTMLEditor) ||
-      (!EditorBase::IsTextNode(pointToInsert.GetContainer()) &&
-       !mHTMLEditor->CanContainTag(*pointToInsert.GetContainer(),
-                                   *nsGkAtoms::textTagName))) {
+  if (!EditorBase::IsTextNode(pointToInsert.GetContainer()) &&
+      !htmlEditor->CanContainTag(*pointToInsert.GetContainer(),
+                                 *nsGkAtoms::textTagName)) {
     return NS_ERROR_FAILURE;
   }
 
   if (aAction == EditAction::insertIMEText) {
     // Right now the WSRunObject code bails on empty strings, but IME needs
     // the InsertTextImpl() call to still happen since empty strings are meaningful there.
-    NS_ENSURE_STATE(mHTMLEditor);
     // If there is one or more IME selections, its minimum offset should be
     // the insertion point.
     int32_t IMESelectionOffset =
-      mHTMLEditor->GetIMESelectionStartOffsetIn(pointToInsert.GetContainer());
+      htmlEditor->GetIMESelectionStartOffsetIn(pointToInsert.GetContainer());
     if (IMESelectionOffset >= 0) {
       pointToInsert.Set(pointToInsert.GetContainer(), IMESelectionOffset);
     }
 
     if (inString->IsEmpty()) {
-      rv = mHTMLEditor->InsertTextImpl(*doc, *inString,
-                                       EditorRawDOMPoint(pointToInsert));
+      rv = htmlEditor->InsertTextImpl(*doc, *inString,
+                                      EditorRawDOMPoint(pointToInsert));
       if (NS_WARN_IF(NS_FAILED(rv))) {
         return rv;
       }
       return NS_OK;
     }
 
-    WSRunObject wsObj(mHTMLEditor, pointToInsert);
+    WSRunObject wsObj(htmlEditor, pointToInsert);
     rv = wsObj.InsertText(*doc, *inString, pointToInsert);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
     return NS_OK;
   }
 
   // aAction == kInsertText
@@ -1378,26 +1387,24 @@ HTMLEditRules::WillInsertText(EditAction
 
   // turn off the edit listener: we know how to
   // build the "doc changed range" ourselves, and it's
   // must faster to do it once here than to track all
   // the changes one at a time.
   AutoLockListener lockit(&mListenerEnabled);
 
   // don't change my selection in subtransactions
-  NS_ENSURE_STATE(mHTMLEditor);
-  AutoTransactionsConserveSelection dontChangeMySelection(mHTMLEditor);
+  AutoTransactionsConserveSelection dontChangeMySelection(htmlEditor);
   nsAutoString tString(*inString);
   const char16_t *unicodeBuf = tString.get();
   int32_t pos = 0;
   NS_NAMED_LITERAL_STRING(newlineStr, LFSTR);
 
   {
-    NS_ENSURE_STATE(mHTMLEditor);
-    AutoTrackDOMPoint tracker(mHTMLEditor->mRangeUpdater, &pointToInsert);
+    AutoTrackDOMPoint tracker(htmlEditor->mRangeUpdater, &pointToInsert);
 
     // for efficiency, break out the pre case separately.  This is because
     // its a lot cheaper to search the input string for only newlines than
     // it is to search for both tabs and newlines.
     if (isPRE || IsPlaintextEditor()) {
       while (unicodeBuf && pos != -1 &&
              pos < static_cast<int32_t>(inString->Length())) {
         int32_t oldPos = pos;
@@ -1414,21 +1421,22 @@ HTMLEditRules::WillInsertText(EditAction
           subStrLen = tString.Length() - oldPos;
           pos = tString.Length();
         }
 
         nsDependentSubstring subStr(tString, oldPos, subStrLen);
 
         // is it a return?
         if (subStr.Equals(newlineStr)) {
-          NS_ENSURE_STATE(mHTMLEditor);
           nsCOMPtr<Element> br =
-            mHTMLEditor->CreateBRImpl(*aSelection, currentPoint,
-                                      nsIEditor::eNone);
-          NS_ENSURE_STATE(br);
+            htmlEditor->CreateBRImpl(*aSelection, currentPoint,
+                                     nsIEditor::eNone);
+          if (NS_WARN_IF(!br)) {
+            return NS_ERROR_FAILURE;
+          }
           pos++;
           if (br->GetNextSibling()) {
             pointToInsert.Set(br->GetNextSibling());
           } else {
             pointToInsert.SetToEndOf(currentPoint.GetContainer());
           }
           // XXX In most cases, pointToInsert and currentPoint are same here.
           //     But if the <br> element has been moved to different point by
@@ -1436,22 +1444,23 @@ HTMLEditRules::WillInsertText(EditAction
           currentPoint.Set(br);
           DebugOnly<bool> advanced = currentPoint.AdvanceOffset();
           NS_WARNING_ASSERTION(advanced,
             "Failed to advance offset after the new <br> element");
           NS_WARNING_ASSERTION(currentPoint == pointToInsert,
             "Perhaps, <br> element position has been moved to different point "
             "by mutation observer");
         } else {
-          NS_ENSURE_STATE(mHTMLEditor);
           EditorRawDOMPoint pointAfterInsertedString;
-          rv = mHTMLEditor->InsertTextImpl(*doc, subStr,
-                                           EditorRawDOMPoint(currentPoint),
-                                           &pointAfterInsertedString);
-          NS_ENSURE_SUCCESS(rv, rv);
+          rv = htmlEditor->InsertTextImpl(*doc, subStr,
+                                          EditorRawDOMPoint(currentPoint),
+                                          &pointAfterInsertedString);
+          if (NS_WARN_IF(NS_FAILED(rv))) {
+            return rv;
+          }
           currentPoint = pointAfterInsertedString;
           pointToInsert = pointAfterInsertedString;
         }
       }
     } else {
       NS_NAMED_LITERAL_STRING(tabStr, "\t");
       NS_NAMED_LITERAL_STRING(spacesStr, "    ");
       char specialChars[] = {TAB, nsCRT::LF, 0};
@@ -1468,18 +1477,17 @@ HTMLEditRules::WillInsertText(EditAction
             subStrLen = 1;
           }
         } else {
           subStrLen = tString.Length() - oldPos;
           pos = tString.Length();
         }
 
         nsDependentSubstring subStr(tString, oldPos, subStrLen);
-        NS_ENSURE_STATE(mHTMLEditor);
-        WSRunObject wsObj(mHTMLEditor, currentPoint);
+        WSRunObject wsObj(htmlEditor, currentPoint);
 
         // is it a tab?
         if (subStr.Equals(tabStr)) {
           EditorRawDOMPoint pointAfterInsertedSpaces;
           rv = wsObj.InsertText(*doc, spacesStr, currentPoint,
                                 &pointAfterInsertedSpaces);
           if (NS_WARN_IF(NS_FAILED(rv))) {
             return rv;
@@ -1623,18 +1631,20 @@ HTMLEditRules::WillInsertBreak(Selection
   *aHandled = false;
 
   NS_ENSURE_STATE(mHTMLEditor);
   RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);
 
   // If the selection isn't collapsed, delete it.
   if (!aSelection.Collapsed()) {
     nsresult rv =
-      htmlEditor->DeleteSelection(nsIEditor::eNone, nsIEditor::eStrip);
-    NS_ENSURE_SUCCESS(rv, rv);
+      htmlEditor->DeleteSelectionAsAction(nsIEditor::eNone, nsIEditor::eStrip);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
   }
 
   WillInsert(aSelection, aCancel);
 
   // Initialize out param.  We want to ignore result of WillInsert().
   *aCancel = false;
 
   // Split any mailcites in the way.  Should we abort this if we encounter
--- a/editor/libeditor/HTMLEditor.cpp
+++ b/editor/libeditor/HTMLEditor.cpp
@@ -707,21 +707,23 @@ HTMLEditor::HandleKeyPressEvent(WidgetKe
              "HandleKeyPressEvent gets non-keypress event");
 
   switch (aKeyboardEvent->mKeyCode) {
     case NS_VK_META:
     case NS_VK_WIN:
     case NS_VK_SHIFT:
     case NS_VK_CONTROL:
     case NS_VK_ALT:
-    case NS_VK_BACK:
-    case NS_VK_DELETE:
       // These keys are handled on EditorBase, so, we can bypass
       // TextEditor.
       return EditorBase::HandleKeyPressEvent(aKeyboardEvent);
+    case NS_VK_BACK:
+    case NS_VK_DELETE:
+      // These keys are handled on TextEditor.
+      return TextEditor::HandleKeyPressEvent(aKeyboardEvent);
     case NS_VK_TAB: {
       if (IsPlaintextEditor()) {
         // If this works as plain text editor, e.g., mail editor for plain
         // text, should be handled on TextEditor.
         return TextEditor::HandleKeyPressEvent(aKeyboardEvent);
       }
 
       if (IsTabbable()) {
@@ -1138,18 +1140,20 @@ HTMLEditor::InsertBR()
 {
   // calling it text insertion to trigger moz br treatment by rules
   AutoRules beginRulesSniffing(this, EditAction::insertText, nsIEditor::eNext);
 
   RefPtr<Selection> selection = GetSelection();
   NS_ENSURE_STATE(selection);
 
   if (!selection->Collapsed()) {
-    nsresult rv = DeleteSelection(nsIEditor::eNone, nsIEditor::eStrip);
-    NS_ENSURE_SUCCESS(rv, rv);
+    nsresult rv = DeleteSelectionAsAction(eNone, eStrip);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
   }
 
   EditorRawDOMPoint atStartOfSelection(GetStartPoint(selection));
   if (NS_WARN_IF(!atStartOfSelection.IsSet())) {
     return NS_ERROR_FAILURE;
   }
 
   // CreateBRImpl() will set selection after the new <br> element.
@@ -1544,18 +1548,20 @@ HTMLEditor::InsertElementAtSelection(nsI
 
   if (!handled) {
     if (aDeleteSelection) {
       if (!IsBlockNode(element)) {
         // E.g., inserting an image.  In this case we don't need to delete any
         // inline wrappers before we do the insertion.  Otherwise we let
         // DeleteSelectionAndPrepareToCreateNode do the deletion for us, which
         // calls DeleteSelection with aStripWrappers = eStrip.
-        rv = DeleteSelection(nsIEditor::eNone, nsIEditor::eNoStrip);
-        NS_ENSURE_SUCCESS(rv, rv);
+        rv = DeleteSelectionAsAction(eNone, eNoStrip);
+        if (NS_WARN_IF(NS_FAILED(rv))) {
+          return rv;
+        }
       }
 
       nsresult rv = DeleteSelectionAndPrepareToCreateNode();
       NS_ENSURE_SUCCESS(rv, rv);
     }
 
     // If deleting, selection will be collapsed.
     // so if not, we collapse it
@@ -3130,23 +3136,23 @@ HTMLEditor::GetEmbeddedObjects(nsIArray*
      }
      iter->Next();
    }
 
   nodes.forget(aNodeList);
   return rv;
 }
 
-NS_IMETHODIMP
+nsresult
 HTMLEditor::DeleteSelectionImpl(EDirection aAction,
                                 EStripWrappers aStripWrappers)
 {
   MOZ_ASSERT(aStripWrappers == eStrip || aStripWrappers == eNoStrip);
 
-  nsresult rv = EditorBase::DeleteSelectionImpl(aAction, aStripWrappers);
+  nsresult rv = TextEditor::DeleteSelectionImpl(aAction, aStripWrappers);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // If we weren't asked to strip any wrappers, we're done.
   if (aStripWrappers == eNoStrip) {
     return NS_OK;
   }
 
   RefPtr<Selection> selection = GetSelection();
@@ -3419,17 +3425,17 @@ HTMLEditor::StyleSheetLoaded(StyleSheet*
 
   return NS_OK;
 }
 
 /**
  * All editor operations which alter the doc should be prefaced
  * with a call to StartOperation, naming the action and direction.
  */
-NS_IMETHODIMP
+nsresult
 HTMLEditor::StartOperation(EditAction opID,
                            nsIEditor::EDirection aDirection)
 {
   // Protect the edit rules object from dying
   RefPtr<TextEditRules> rules(mRules);
 
   EditorBase::StartOperation(opID, aDirection);  // will set mAction, mDirection
   if (rules) {
@@ -3437,17 +3443,17 @@ HTMLEditor::StartOperation(EditAction op
   }
   return NS_OK;
 }
 
 /**
  * All editor operations which alter the doc should be followed
  * with a call to EndOperation.
  */
-NS_IMETHODIMP
+nsresult
 HTMLEditor::EndOperation()
 {
   // Protect the edit rules object from dying
   RefPtr<TextEditRules> rules(mRules);
 
   // post processing
   nsresult rv = rules ? rules->AfterEdit(mAction, mDirection) : NS_OK;
   EditorBase::EndOperation();  // will clear mAction, mDirection
--- a/editor/libeditor/HTMLEditor.h
+++ b/editor/libeditor/HTMLEditor.h
@@ -345,24 +345,24 @@ public:
 
   NS_IMETHOD DebugUnitTests(int32_t* outNumTests,
                             int32_t* outNumTestsFailed) override;
 
   /**
    * All editor operations which alter the doc should be prefaced
    * with a call to StartOperation, naming the action and direction.
    */
-  NS_IMETHOD StartOperation(EditAction opID,
-                            nsIEditor::EDirection aDirection) override;
+  virtual nsresult StartOperation(EditAction opID,
+                                  nsIEditor::EDirection aDirection) override;
 
   /**
    * All editor operations which alter the doc should be followed
    * with a call to EndOperation.
    */
-  NS_IMETHOD EndOperation() override;
+  virtual nsresult EndOperation() override;
 
   /**
    * returns true if aParentTag can contain a child of type aChildTag.
    */
   virtual bool TagCanContainTag(nsAtom& aParentTag,
                                 nsAtom& aChildTag) const override;
 
   /**
@@ -378,18 +378,18 @@ public:
   /**
    * Join together any adjacent editable text nodes in the range.
    */
   nsresult CollapseAdjacentTextNodes(nsRange* aRange);
 
   virtual bool AreNodesSameType(nsIContent* aNode1,
                                 nsIContent* aNode2) override;
 
-  NS_IMETHOD DeleteSelectionImpl(EDirection aAction,
-                                 EStripWrappers aStripWrappers) override;
+  virtual nsresult DeleteSelectionImpl(EDirection aAction,
+                                       EStripWrappers aStripWrappers) override;
 
   /**
    * DeleteNodeWithTransaction() removes aNode from the DOM tree if it's
    * modifiable.  Note that this is not an override of same method of
    * EditorBase.
    *
    * @param aNode       The node to be removed from the DOM tree.
    */
--- a/editor/libeditor/HTMLEditorDataTransfer.cpp
+++ b/editor/libeditor/HTMLEditorDataTransfer.cpp
@@ -109,18 +109,20 @@ HTMLEditor::LoadHTML(const nsAString& aI
   NS_ENSURE_SUCCESS(rv, rv);
   if (cancel) {
     return NS_OK; // rules canceled the operation
   }
 
   if (!handled) {
     // Delete Selection, but only if it isn't collapsed, see bug #106269
     if (!selection->Collapsed()) {
-      rv = DeleteSelection(eNone, eStrip);
-      NS_ENSURE_SUCCESS(rv, rv);
+      rv = DeleteSelectionAsAction(eNone, eStrip);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
     }
 
     // Get the first range in the selection, for context:
     RefPtr<nsRange> range = selection->GetRangeAt(0);
     NS_ENSURE_TRUE(range, NS_ERROR_NULL_POINTER);
 
     // Create fragment for pasted HTML.
     ErrorResult error;
@@ -248,18 +250,20 @@ HTMLEditor::DoInsertHTMLWithContext(cons
   // also occur later; this block is intended to cover the various
   // scenarios where we are dropping in an editor (and may want to delete
   // the selection before collapsing the selection in the new destination)
   if (aDestNode) {
     if (aDeleteSelection) {
       // Use an auto tracker so that our drop point is correctly
       // positioned after the delete.
       AutoTrackDOMPoint tracker(mRangeUpdater, &targetPoint);
-      rv = DeleteSelection(eNone, eStrip);
-      NS_ENSURE_SUCCESS(rv, rv);
+      rv = DeleteSelectionAsAction(eNone, eStrip);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
     }
 
     ErrorResult error;
     selection->Collapse(targetPoint, error);
     if (NS_WARN_IF(error.Failed())) {
       return error.StealNSResult();
     }
   }
@@ -275,17 +279,20 @@ HTMLEditor::DoInsertHTMLWithContext(cons
                            streamStartOffset,
                            streamEndParent,
                            streamEndOffset);
 
   if (nodeList.IsEmpty()) {
     // We aren't inserting anything, but if aDeleteSelection is set, we do want
     // to delete everything.
     if (aDeleteSelection) {
-      return DeleteSelection(eNone, eStrip);
+      nsresult rv = DeleteSelectionAsAction(eNone, eStrip);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
     }
     return NS_OK;
   }
 
   // Are there any table elements in the list?
   // check for table cell selection mode
   bool cellSelectionMode = false;
   nsCOMPtr<nsIDOMElement> cell;
--- a/editor/libeditor/HTMLTableEditor.cpp
+++ b/editor/libeditor/HTMLTableEditor.cpp
@@ -694,17 +694,21 @@ HTMLEditor::DeleteTable2(nsIDOMElement* 
   // Select the table
   nsresult rv = ClearSelection();
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
   rv = AppendNodeToSelectionAsRange(aTable);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  return DeleteSelection(nsIEditor::eNext, nsIEditor::eStrip);
+  rv = DeleteSelectionAsAction(eNext, eStrip);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+  return NS_OK;
 }
 
 NS_IMETHODIMP
 HTMLEditor::DeleteTable()
 {
   RefPtr<Selection> selection;
   nsCOMPtr<nsIDOMElement> table;
   nsresult rv = GetCellContext(getter_AddRefs(selection),
--- a/editor/libeditor/TextEditRules.cpp
+++ b/editor/libeditor/TextEditRules.cpp
@@ -433,17 +433,18 @@ TextEditRules::WillInsertBreak(Selection
       return NS_OK;
     }
 
     *aCancel = false;
 
     // if the selection isn't collapsed, delete it.
     if (!aSelection->IsCollapsed()) {
       NS_ENSURE_STATE(mTextEditor);
-      rv = mTextEditor->DeleteSelection(nsIEditor::eNone, nsIEditor::eStrip);
+      rv = mTextEditor->DeleteSelectionAsAction(nsIEditor::eNone,
+                                                nsIEditor::eStrip);
       NS_ENSURE_SUCCESS(rv, rv);
     }
 
     WillInsert(*aSelection, aCancel);
     // initialize out param
     // we want to ignore result of WillInsert()
     *aCancel = false;
   }
@@ -680,17 +681,18 @@ TextEditRules::WillInsertText(EditAction
     nsContentUtils::GetSelectionInTextControl(aSelection,
                                               mTextEditor->GetRoot(),
                                               start, end);
   }
 
   // if the selection isn't collapsed, delete it.
   if (!aSelection->IsCollapsed()) {
     NS_ENSURE_STATE(mTextEditor);
-    rv = mTextEditor->DeleteSelection(nsIEditor::eNone, nsIEditor::eStrip);
+    rv = mTextEditor->DeleteSelectionAsAction(nsIEditor::eNone,
+                                              nsIEditor::eStrip);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   WillInsert(*aSelection, aCancel);
   // initialize out param
   // we want to ignore result of WillInsert()
   *aCancel = false;
 
--- a/editor/libeditor/TextEditor.cpp
+++ b/editor/libeditor/TextEditor.cpp
@@ -1,28 +1,31 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* 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 "mozilla/TextEditor.h"
 
+#include "EditAggregateTransaction.h"
+#include "HTMLEditRules.h"
 #include "InternetCiter.h"
 #include "TextEditUtils.h"
 #include "gfxFontUtils.h"
 #include "mozilla/Assertions.h"
 #include "mozilla/EditAction.h"
 #include "mozilla/EditorDOMPoint.h"
 #include "mozilla/EditorUtils.h" // AutoPlaceholderBatch, AutoRules
 #include "mozilla/HTMLEditor.h"
 #include "mozilla/mozalloc.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/TextEditRules.h"
 #include "mozilla/TextComposition.h"
 #include "mozilla/TextEvents.h"
+#include "mozilla/TextServicesDocument.h"
 #include "mozilla/dom/Selection.h"
 #include "mozilla/dom/Event.h"
 #include "mozilla/dom/Element.h"
 #include "nsAString.h"
 #include "nsCRT.h"
 #include "nsCaret.h"
 #include "nsCharTraits.h"
 #include "nsComponentManagerUtils.h"
@@ -351,20 +354,38 @@ TextEditor::HandleKeyPressEvent(WidgetKe
              "HandleKeyPressEvent gets non-keypress event");
 
   switch (aKeyboardEvent->mKeyCode) {
     case NS_VK_META:
     case NS_VK_WIN:
     case NS_VK_SHIFT:
     case NS_VK_CONTROL:
     case NS_VK_ALT:
-    case NS_VK_BACK:
-    case NS_VK_DELETE:
       // These keys are handled on EditorBase
       return EditorBase::HandleKeyPressEvent(aKeyboardEvent);
+    case NS_VK_BACK:
+      if (aKeyboardEvent->IsControl() || aKeyboardEvent->IsAlt() ||
+          aKeyboardEvent->IsMeta() || aKeyboardEvent->IsOS()) {
+        return NS_OK;
+      }
+      DeleteSelectionAsAction(nsIEditor::ePrevious, nsIEditor::eStrip);
+      aKeyboardEvent->PreventDefault(); // consumed
+      return NS_OK;
+    case NS_VK_DELETE:
+      // on certain platforms (such as windows) the shift key
+      // modifies what delete does (cmd_cut in this case).
+      // bailing here to allow the keybindings to do the cut.
+      if (aKeyboardEvent->IsShift() || aKeyboardEvent->IsControl() ||
+          aKeyboardEvent->IsAlt() || aKeyboardEvent->IsMeta() ||
+          aKeyboardEvent->IsOS()) {
+        return NS_OK;
+      }
+      DeleteSelectionAsAction(nsIEditor::eNext, nsIEditor::eStrip);
+      aKeyboardEvent->PreventDefault(); // consumed
+      return NS_OK;
     case NS_VK_TAB: {
       if (IsTabbable()) {
         return NS_OK; // let it be used for focus switching
       }
 
       if (aKeyboardEvent->IsShift() || aKeyboardEvent->IsControl() ||
           aKeyboardEvent->IsAlt() || aKeyboardEvent->IsMeta() ||
           aKeyboardEvent->IsOS()) {
@@ -623,69 +644,294 @@ TextEditor::ExtendSelectionForDelete(Sel
       // For avoiding several compiler warnings
       default:
         return NS_OK;
     }
   }
   return NS_OK;
 }
 
-nsresult
+NS_IMETHODIMP
 TextEditor::DeleteSelection(EDirection aAction,
                             EStripWrappers aStripWrappers)
 {
+  nsresult rv = DeleteSelectionAsAction(aAction, aStripWrappers);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+  return NS_OK;
+}
+
+nsresult
+TextEditor::DeleteSelectionAsAction(EDirection aDirection,
+                                    EStripWrappers aStripWrappers)
+{
   MOZ_ASSERT(aStripWrappers == eStrip || aStripWrappers == eNoStrip);
 
   if (!mRules) {
     return NS_ERROR_NOT_INITIALIZED;
   }
 
   // Protect the edit rules object from dying
   RefPtr<TextEditRules> rules(mRules);
 
   // delete placeholder txns merge.
   AutoPlaceholderBatch batch(this, nsGkAtoms::DeleteTxnName);
-  AutoRules beginRulesSniffing(this, EditAction::deleteSelection, aAction);
+  AutoRules beginRulesSniffing(this, EditAction::deleteSelection, aDirection);
 
   // pre-process
   RefPtr<Selection> selection = GetSelection();
   NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
 
   // If there is an existing selection when an extended delete is requested,
   //  platforms that use "caret-style" caret positioning collapse the
   //  selection to the  start and then create a new selection.
   //  Platforms that use "selection-style" caret positioning just delete the
   //  existing selection without extending it.
-  if (!selection->Collapsed() &&
-      (aAction == eNextWord || aAction == ePreviousWord ||
-       aAction == eToBeginningOfLine || aAction == eToEndOfLine)) {
-    if (mCaretStyle == 1) {
-      nsresult rv = selection->CollapseToStart();
-      NS_ENSURE_SUCCESS(rv, rv);
-    } else {
-      aAction = eNone;
+  if (!selection->Collapsed()) {
+    switch (aDirection) {
+      case eNextWord:
+      case ePreviousWord:
+      case eToBeginningOfLine:
+      case eToEndOfLine: {
+        if (mCaretStyle != 1) {
+          aDirection = eNone;
+          break;
+        }
+        ErrorResult error;
+        selection->CollapseToStart(error);
+        if (NS_WARN_IF(error.Failed())) {
+          return error.StealNSResult();
+        }
+        break;
+      }
+      default:
+        break;
     }
   }
 
   RulesInfo ruleInfo(EditAction::deleteSelection);
-  ruleInfo.collapsedAction = aAction;
+  ruleInfo.collapsedAction = aDirection;
   ruleInfo.stripWrappers = aStripWrappers;
   bool cancel, handled;
   nsresult rv = rules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
   NS_ENSURE_SUCCESS(rv, rv);
   if (!cancel && !handled) {
-    rv = DeleteSelectionImpl(aAction, aStripWrappers);
+    rv = DeleteSelectionImpl(aDirection, aStripWrappers);
   }
   if (!cancel) {
     // post-process
     rv = rules->DidDoAction(selection, &ruleInfo, rv);
   }
   return rv;
 }
 
+nsresult
+TextEditor::DeleteSelectionImpl(EDirection aDirection,
+                                EStripWrappers aStripWrappers)
+{
+  MOZ_ASSERT(aStripWrappers == eStrip || aStripWrappers == eNoStrip);
+
+  RefPtr<Selection> selection = GetSelection();
+  if (NS_WARN_IF(!selection)) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
+
+  RefPtr<EditAggregateTransaction> deleteSelectionTransaction;
+  nsCOMPtr<nsINode> deleteNode;
+  int32_t deleteCharOffset = 0, deleteCharLength = 0;
+  if (!selection->Collapsed() || aDirection != eNone) {
+    deleteSelectionTransaction =
+      CreateTxnForDeleteSelection(aDirection,
+                                  getter_AddRefs(deleteNode),
+                                  &deleteCharOffset,
+                                  &deleteCharLength);
+    if (NS_WARN_IF(!deleteSelectionTransaction)) {
+      return NS_ERROR_FAILURE;
+    }
+  }
+
+  RefPtr<CharacterData> deleteCharData =
+    CharacterData::FromNodeOrNull(deleteNode);
+  AutoRules beginRulesSniffing(this, EditAction::deleteSelection, aDirection);
+
+  if (mRules && mRules->AsHTMLEditRules()) {
+    if (!deleteNode) {
+      RefPtr<HTMLEditRules> htmlEditRules = mRules->AsHTMLEditRules();
+      htmlEditRules->WillDeleteSelection(selection);
+    } else if (!deleteCharData) {
+      RefPtr<HTMLEditRules> htmlEditRules = mRules->AsHTMLEditRules();
+      htmlEditRules->WillDeleteNode(deleteNode);
+    }
+  }
+
+  // Notify nsIEditActionListener::WillDelete[Selection|Text]
+  if (!mActionListeners.IsEmpty()) {
+    if (!deleteNode) {
+      AutoActionListenerArray listeners(mActionListeners);
+      for (auto& listener : listeners) {
+        listener->WillDeleteSelection(selection);
+      }
+    } else if (deleteCharData) {
+      AutoActionListenerArray listeners(mActionListeners);
+      for (auto& listener : listeners) {
+        listener->WillDeleteText(deleteCharData, deleteCharOffset, 1);
+      }
+    }
+  }
+
+  // Delete the specified amount
+  nsresult rv = DoTransaction(deleteSelectionTransaction);
+
+  if (mRules && mRules->AsHTMLEditRules() && deleteCharData) {
+    RefPtr<HTMLEditRules> htmlEditRules = mRules->AsHTMLEditRules();
+    htmlEditRules->DidDeleteText(deleteNode, deleteCharOffset, 1);
+  }
+
+  if (mTextServicesDocument && NS_SUCCEEDED(rv) &&
+      deleteNode && !deleteCharData) {
+    RefPtr<TextServicesDocument> textServicesDocument = mTextServicesDocument;
+    textServicesDocument->DidDeleteNode(deleteNode);
+  }
+
+  // Notify nsIEditActionListener::DidDelete[Selection|Text|Node]
+  {
+    AutoActionListenerArray listeners(mActionListeners);
+    if (!deleteNode) {
+      for (auto& listener : mActionListeners) {
+        listener->DidDeleteSelection(selection);
+      }
+    } else if (deleteCharData) {
+      for (auto& listener : mActionListeners) {
+        listener->DidDeleteText(deleteCharData, deleteCharOffset, 1, rv);
+      }
+    } else {
+      for (auto& listener : mActionListeners) {
+        listener->DidDeleteNode(deleteNode->AsDOMNode(), rv);
+      }
+    }
+  }
+
+  return rv;
+}
+
+already_AddRefed<Element>
+TextEditor::DeleteSelectionAndCreateElement(nsAtom& aTag)
+{
+  nsresult rv = DeleteSelectionAndPrepareToCreateNode();
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return nullptr;
+  }
+
+  RefPtr<Selection> selection = GetSelection();
+  if (NS_WARN_IF(!selection)) {
+    return nullptr;
+  }
+
+  EditorRawDOMPoint pointToInsert(selection->AnchorRef());
+  if (!pointToInsert.IsSet()) {
+    return nullptr;
+  }
+  RefPtr<Element> newElement = CreateNodeWithTransaction(aTag, pointToInsert);
+
+  // We want the selection to be just after the new node
+  EditorRawDOMPoint afterNewElement(newElement);
+  MOZ_ASSERT(afterNewElement.IsSetAndValid());
+  DebugOnly<bool> advanced = afterNewElement.AdvanceOffset();
+  NS_WARNING_ASSERTION(advanced,
+                       "Failed to move offset next to the new element");
+  ErrorResult error;
+  selection->Collapse(afterNewElement, error);
+  if (NS_WARN_IF(error.Failed())) {
+    // XXX Even if it succeeded to create new element, this returns error
+    //     when Selection.Collapse() fails something.  This could occur with
+    //     mutation observer or mutation event listener.
+    error.SuppressException();
+    return nullptr;
+  }
+  return newElement.forget();
+}
+
+nsresult
+TextEditor::DeleteSelectionAndPrepareToCreateNode()
+{
+  RefPtr<Selection> selection = GetSelection();
+  if (NS_WARN_IF(!selection)) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
+
+  if (NS_WARN_IF(!selection->GetAnchorFocusRange())) {
+    return NS_OK;
+  }
+
+  if (!selection->GetAnchorFocusRange()->Collapsed()) {
+    nsresult rv = DeleteSelectionAsAction(eNone, eStrip);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+    MOZ_ASSERT(selection->GetAnchorFocusRange() &&
+               selection->GetAnchorFocusRange()->Collapsed(),
+               "Selection not collapsed after delete");
+  }
+
+  // If the selection is a chardata node, split it if necessary and compute
+  // where to put the new node
+  EditorDOMPoint atAnchor(selection->AnchorRef());
+  if (NS_WARN_IF(!atAnchor.IsSet()) || !atAnchor.IsInDataNode()) {
+    return NS_OK;
+  }
+
+  if (NS_WARN_IF(!atAnchor.GetContainer()->GetParentNode())) {
+    return NS_ERROR_FAILURE;
+  }
+
+  if (atAnchor.IsStartOfContainer()) {
+    EditorRawDOMPoint atAnchorContainer(atAnchor.GetContainer());
+    if (NS_WARN_IF(!atAnchorContainer.IsSetAndValid())) {
+      return NS_ERROR_FAILURE;
+    }
+    ErrorResult error;
+    selection->Collapse(atAnchorContainer, error);
+    if (NS_WARN_IF(error.Failed())) {
+      return error.StealNSResult();
+    }
+    return NS_OK;
+  }
+
+  if (atAnchor.IsEndOfContainer()) {
+    EditorRawDOMPoint afterAnchorContainer(atAnchor.GetContainer());
+    if (NS_WARN_IF(!afterAnchorContainer.AdvanceOffset())) {
+      return NS_ERROR_FAILURE;
+    }
+    ErrorResult error;
+    selection->Collapse(afterAnchorContainer, error);
+    if (NS_WARN_IF(error.Failed())) {
+      return error.StealNSResult();
+    }
+    return NS_OK;
+  }
+
+  ErrorResult error;
+  nsCOMPtr<nsIContent> newLeftNode = SplitNodeWithTransaction(atAnchor, error);
+  if (NS_WARN_IF(error.Failed())) {
+    return error.StealNSResult();
+  }
+
+  EditorRawDOMPoint atRightNode(atAnchor.GetContainer());
+  if (NS_WARN_IF(!atRightNode.IsSet())) {
+    return NS_ERROR_FAILURE;
+  }
+  MOZ_ASSERT(atRightNode.IsSetAndValid());
+  selection->Collapse(atRightNode, error);
+  if (NS_WARN_IF(error.Failed())) {
+    return error.StealNSResult();
+  }
+  return NS_OK;
+}
+
 NS_IMETHODIMP
 TextEditor::InsertText(const nsAString& aStringToInsert)
 {
   if (!mRules) {
     return NS_ERROR_NOT_INITIALIZED;
   }
 
   // Protect the edit rules object from dying
@@ -853,17 +1099,18 @@ TextEditor::SetText(const nsAString& aSt
         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);
+        rv = DeleteSelectionAsAction(eNone, eStrip);
+        NS_WARNING_ASSERTION(NS_FAILED(rv), "Failed to remove all text");
       } else {
         rv = InsertText(aString);
       }
     }
   }
   // post-process
   return rules->DidDoAction(selection, &ruleInfo, rv);
 }
@@ -1280,17 +1527,17 @@ TextEditor::FireClipboardEvent(EventMess
   return !mDidPreDestroy;
 }
 
 NS_IMETHODIMP
 TextEditor::Cut()
 {
   bool actionTaken = false;
   if (FireClipboardEvent(eCut, nsIClipboard::kGlobalClipboard, &actionTaken)) {
-    DeleteSelection(eNone, eStrip);
+    DeleteSelectionAsAction(eNone, eStrip);
   }
   return actionTaken ? NS_OK : NS_ERROR_FAILURE;
 }
 
 NS_IMETHODIMP
 TextEditor::CanCut(bool* aCanCut)
 {
   NS_ENSURE_ARG_POINTER(aCanCut);
@@ -1663,17 +1910,17 @@ TextEditor::GetEmbeddedObjects(nsIArray*
   *aNodeList = nullptr;
   return NS_OK;
 }
 
 /**
  * All editor operations which alter the doc should be prefaced
  * with a call to StartOperation, naming the action and direction.
  */
-NS_IMETHODIMP
+nsresult
 TextEditor::StartOperation(EditAction opID,
                            nsIEditor::EDirection aDirection)
 {
   // Protect the edit rules object from dying
   RefPtr<TextEditRules> rules(mRules);
 
   EditorBase::StartOperation(opID, aDirection);  // will set mAction, mDirection
   if (rules) {
@@ -1681,17 +1928,17 @@ TextEditor::StartOperation(EditAction op
   }
   return NS_OK;
 }
 
 /**
  * All editor operations which alter the doc should be followed
  * with a call to EndOperation.
  */
-NS_IMETHODIMP
+nsresult
 TextEditor::EndOperation()
 {
   // Protect the edit rules object from dying
   RefPtr<TextEditRules> rules(mRules);
 
   // post processing
   nsresult rv = rules ? rules->AfterEdit(mAction, mDirection) : NS_OK;
   EditorBase::EndOperation();  // will clear mAction, mDirection
--- a/editor/libeditor/TextEditor.h
+++ b/editor/libeditor/TextEditor.h
@@ -54,33 +54,17 @@ public:
   TextEditor();
 
   // nsIPlaintextEditor methods
   NS_DECL_NSIPLAINTEXTEDITOR
 
   // nsIEditorMailSupport overrides
   NS_DECL_NSIEDITORMAILSUPPORT
 
-  // Overrides of EditorBase
-  virtual nsresult RemoveAttributeOrEquivalent(
-                     Element* aElement,
-                     nsAtom* aAttribute,
-                     bool aSuppressTransaction) override;
-  virtual nsresult SetAttributeOrEquivalent(Element* aElement,
-                                            nsAtom* aAttribute,
-                                            const nsAString& aValue,
-                                            bool aSuppressTransaction) override;
-  using EditorBase::RemoveAttributeOrEquivalent;
-  using EditorBase::SetAttributeOrEquivalent;
-
-  virtual nsresult Init(nsIDocument& aDoc, Element* aRoot,
-                        nsISelectionController* aSelCon, uint32_t aFlags,
-                        const nsAString& aValue) override;
-
-  nsresult DocumentIsEmpty(bool* aIsEmpty);
+  // Overrides of nsIEditor
   NS_IMETHOD GetDocumentIsEmpty(bool* aDocumentIsEmpty) override;
 
   NS_IMETHOD DeleteSelection(EDirection aAction,
                              EStripWrappers aStripWrappers) override;
 
   NS_IMETHOD SetDocumentCharacterSet(const nsACString& characterSet) override;
 
   // If there are some good name to create non-virtual Undo()/Redo() methods,
@@ -103,28 +87,46 @@ public:
                             uint32_t aFlags,
                             nsAString& aOutputString) override;
 
   NS_IMETHOD OutputToStream(nsIOutputStream* aOutputStream,
                             const nsAString& aFormatType,
                             const nsACString& aCharsetOverride,
                             uint32_t aFlags) override;
 
+  // Overrides of EditorBase
+  virtual nsresult RemoveAttributeOrEquivalent(
+                     Element* aElement,
+                     nsAtom* aAttribute,
+                     bool aSuppressTransaction) override;
+  virtual nsresult SetAttributeOrEquivalent(Element* aElement,
+                                            nsAtom* aAttribute,
+                                            const nsAString& aValue,
+                                            bool aSuppressTransaction) override;
+  using EditorBase::RemoveAttributeOrEquivalent;
+  using EditorBase::SetAttributeOrEquivalent;
+
+  virtual nsresult Init(nsIDocument& aDoc, Element* aRoot,
+                        nsISelectionController* aSelCon, uint32_t aFlags,
+                        const nsAString& aValue) override;
+
+  nsresult DocumentIsEmpty(bool* aIsEmpty);
+
   /**
    * All editor operations which alter the doc should be prefaced
    * with a call to StartOperation, naming the action and direction.
    */
-  NS_IMETHOD StartOperation(EditAction opID,
-                            nsIEditor::EDirection aDirection) override;
+  virtual nsresult StartOperation(EditAction opID,
+                                  nsIEditor::EDirection aDirection) override;
 
   /**
    * All editor operations which alter the doc should be followed
    * with a call to EndOperation.
    */
-  NS_IMETHOD EndOperation() override;
+  virtual nsresult EndOperation() override;
 
   /**
    * Make the given selection span the entire document.
    */
   virtual nsresult SelectEntireDocument(Selection* aSelection) override;
 
   virtual nsresult HandleKeyPressEvent(
                      WidgetKeyboardEvent* aKeyboardEvent) override;
@@ -132,16 +134,31 @@ public:
   virtual dom::EventTarget* GetDOMEventTarget() override;
 
   virtual nsresult BeginIMEComposition(WidgetCompositionEvent* aEvent) override;
   virtual nsresult UpdateIMEComposition(
                      WidgetCompositionEvent* aCompositionChangeEvet) override;
 
   virtual already_AddRefed<nsIContent> GetInputEventTargetContent() override;
 
+  /**
+   * DeleteSelectionAsAction() removes selection content or content around
+   * caret with transactions.  This should be used for handling it as an
+   * edit action.
+   *
+   * @param aDirection          How much range should be removed.
+   * @param aStripWrappers      Whether the parent blocks should be removed
+   *                            when they become empty.
+   */
+  nsresult DeleteSelectionAsAction(EDirection aDirection,
+                                   EStripWrappers aStripWrappers);
+
+  virtual nsresult DeleteSelectionImpl(EDirection aAction,
+                                       EStripWrappers aStripWrappers);
+
   // Utility Routines, not part of public API
   NS_IMETHOD TypedText(const nsAString& aString, ETypingAction aAction);
 
   nsresult InsertTextAt(const nsAString& aStringToInsert,
                         nsINode* aDestinationNode,
                         int32_t aDestOffset,
                         bool aDoDeleteSelection);
 
@@ -243,16 +260,34 @@ protected:
   /**
    * Factored methods for handling insertion of data from transferables
    * (drag&drop or clipboard).
    */
   NS_IMETHOD PrepareTransferable(nsITransferable** transferable);
   nsresult InsertTextFromTransferable(nsITransferable* transferable);
 
   /**
+   * DeleteSelectionAndCreateElement() creates a element whose name is aTag.
+   * And insert it into the DOM tree after removing the selected content.
+   *
+   * @param aTag                The element name to be created.
+   * @return                    Created new element.
+   */
+  already_AddRefed<Element> DeleteSelectionAndCreateElement(nsAtom& aTag);
+
+  /**
+   * This method first deletes the selection, if it's not collapsed.  Then if
+   * the selection lies in a CharacterData node, it splits it.  If the
+   * selection is at this point collapsed in a CharacterData node, it's
+   * adjusted to be collapsed right before or after the node instead (which is
+   * always possible, since the node was split).
+   */
+  nsresult DeleteSelectionAndPrepareToCreateNode();
+
+  /**
    * Shared outputstring; returns whether selection is collapsed and resulting
    * string.
    */
   nsresult SharedOutputString(uint32_t aFlags, bool* aIsCollapsed,
                               nsAString& aResult);
 
   enum PasswordFieldAllowed
   {
--- a/editor/libeditor/TextEditorDataTransfer.cpp
+++ b/editor/libeditor/TextEditorDataTransfer.cpp
@@ -77,22 +77,27 @@ TextEditor::InsertTextAt(const nsAString
 
     nsCOMPtr<nsINode> targetNode = aDestinationNode;
     int32_t targetOffset = aDestOffset;
 
     if (aDoDeleteSelection) {
       // Use an auto tracker so that our drop point is correctly
       // positioned after the delete.
       AutoTrackDOMPoint tracker(mRangeUpdater, &targetNode, &targetOffset);
-      nsresult rv = DeleteSelection(eNone, eStrip);
-      NS_ENSURE_SUCCESS(rv, rv);
+      nsresult rv = DeleteSelectionAsAction(eNone, eStrip);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
     }
 
-    nsresult rv = selection->Collapse(targetNode, targetOffset);
-    NS_ENSURE_SUCCESS(rv, rv);
+    ErrorResult error;
+    selection->Collapse(RawRangeBoundary(targetNode, targetOffset), error);
+    if (NS_WARN_IF(error.Failed())) {
+      return error.StealNSResult();
+    }
   }
 
   return InsertText(aStringToInsert);
 }
 
 nsresult
 TextEditor::InsertTextFromTransferable(nsITransferable* aTransferable)
 {
--- a/editor/spellchecker/TextServicesDocument.cpp
+++ b/editor/spellchecker/TextServicesDocument.cpp
@@ -1094,17 +1094,18 @@ TextServicesDocument::DeleteSelection()
 
   // Make sure mIterator always points to something valid!
 
   AdjustContentIterator();
 
   // Now delete the actual content!
   RefPtr<TextEditor> textEditor = mTextEditor;
   nsresult rv =
-    textEditor->DeleteSelection(nsIEditor::ePrevious, nsIEditor::eStrip);
+    textEditor->DeleteSelectionAsAction(nsIEditor::ePrevious,
+                                        nsIEditor::eStrip);
   if (NS_FAILED(rv)) {
     UNLOCK_DOC(this);
     return rv;
   }
 
   // Now that we've actually deleted the selected content,
   // check to see if our mExtent has changed, if so, then
   // we have to create a new content iterator!