Bug 1463327 - part 1: Change scope of some methods of EditorBase which won't be called by non-helper classes of editing to protected r=m_kato
authorMasayuki Nakano <masayuki@d-toybox.com>
Tue, 22 May 2018 16:08:43 +0900
changeset 473908 5c9278b7b027fd70beddb2a30e0a8fa6842398d9
parent 473907 916908593f7387cb2e0f3217c2bd29813283eba0
child 473909 f0cb4203b12d3725c7065d109a4b90765bb70ea0
push id9374
push userjlund@mozilla.com
push dateMon, 18 Jun 2018 21:43:20 +0000
treeherdermozilla-beta@160e085dfb0b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersm_kato
bugs1463327
milestone62.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 1463327 - part 1: Change scope of some methods of EditorBase which won't be called by non-helper classes of editing to protected r=m_kato EditorBase (and other editor classes) have 2 type of public methods. One is true-public methods. I.e., they should be able to be called by anybody. E.g., command handlers, event listeners, or JS via nsIEditor interface. The other is semi-public methods. They are not called by the above examples but called by other classes which are helper classes to handle edit actions. E.g., TextEditRules, HTMLEditRules, HTMLEditUtils, CSSEditUtils and Transaction classes. When we will implement InputEvent.inputType, we need to create new stack class and create its instance at every true-public methods to manage current inputType (like TextEditRules::AutoSafeEditorData). Therefore, it should not happen that new code starts to call semi-public methods without the new stack class instance. For preventing this issue, we should make EditorBase have only the true-public methods as public. The other public methods should be protected and their users should be friend classes. Then, we can protect such method from external classes. Note that this patch just moves the methods without any changes in EditorBase.h (except removes GetName() since there is no body of this method and removes IterDirection since it's unused). MozReview-Commit-ID: HBseKLL6pxx
editor/libeditor/EditorBase.h
editor/libeditor/HTMLEditor.h
editor/libeditor/HTMLStyleEditor.cpp
editor/libeditor/TextEditRules.cpp
editor/libeditor/TextEditRules.h
editor/libeditor/TextEditor.h
--- a/editor/libeditor/EditorBase.h
+++ b/editor/libeditor/EditorBase.h
@@ -50,39 +50,44 @@ class nsITransactionListener;
 class nsIWidget;
 class nsRange;
 
 namespace mozilla {
 class AddStyleSheetTransaction;
 class AutoRules;
 class AutoSelectionRestorer;
 class AutoTransactionsConserveSelection;
+class AutoUpdateViewBatch;
 class ChangeAttributeTransaction;
 class CompositionTransaction;
 class CreateElementTransaction;
+class CSSEditUtils;
 class DeleteNodeTransaction;
 class DeleteTextTransaction;
 class EditAggregateTransaction;
 class EditorEventListener;
 class EditTransactionBase;
 class ErrorResult;
 class HTMLEditor;
+class HTMLEditUtils;
 class IMEContentObserver;
 class InsertNodeTransaction;
 class InsertTextTransaction;
 class JoinNodeTransaction;
 class PlaceholderTransaction;
 class RemoveStyleSheetTransaction;
 class SplitNodeResult;
 class SplitNodeTransaction;
 class TextComposition;
 class TextEditor;
 class TextEditRules;
 class TextInputListener;
 class TextServicesDocument;
+class TypeInState;
+class WSRunObject;
 enum class EditAction : int32_t;
 
 namespace dom {
 class DataTransfer;
 class DragEvent;
 class Element;
 class EventTarget;
 class Text;
@@ -192,39 +197,31 @@ class EditorBase : public nsIEditor
                  , public nsISelectionListener
                  , public nsSupportsWeakReference
 {
 public:
   typedef dom::Element Element;
   typedef dom::Selection Selection;
   typedef dom::Text Text;
 
-  enum IterDirection
-  {
-    kIterForward,
-    kIterBackward
-  };
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(EditorBase, nsIEditor)
+
+  // nsIEditor methods
+  NS_DECL_NSIEDITOR
+
+  // nsISelectionListener method
+  NS_DECL_NSISELECTIONLISTENER
 
   /**
    * The default constructor. This should suffice. the setting of the
    * interfaces is done after the construction of the editor class.
    */
   EditorBase();
 
-protected:
-  /**
-   * The default destructor. This should suffice. Should this be pure virtual
-   * for someone to derive from the EditorBase later? I don't believe so.
-   */
-  virtual ~EditorBase();
-
-public:
-  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
-  NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(EditorBase, nsIEditor)
-
   /**
    * Init is to tell the implementation of nsIEditor to begin its services
    * @param aDoc          The dom document interface being observed
    * @param aRoot         This is the root of the editable section of this
    *                      document. If it is null then we get root
    *                      from document body.
    * @param aSelCon       this should be used to get the selection location
    *                      (will be null for HTML editors)
@@ -232,73 +229,443 @@ public:
    *                      of the editor.
    */
   virtual nsresult Init(nsIDocument& doc,
                         Element* aRoot,
                         nsISelectionController* aSelCon,
                         uint32_t aFlags,
                         const nsAString& aInitialValue);
 
+  /**
+   * PostCreate should be called after Init, and is the time that the editor
+   * tells its documentStateObservers that the document has been created.
+   */
+  nsresult PostCreate();
+
+  /**
+   * PreDestroy is called before the editor goes away, and gives the editor a
+   * chance to tell its documentStateObservers that the document is going away.
+   * @param aDestroyingFrames set to true when the frames being edited
+   * are being destroyed (so there is no need to modify any nsISelections,
+   * nor is it safe to do so)
+   */
+  virtual void PreDestroy(bool aDestroyingFrames);
+
   bool IsInitialized() const { return !!mDocument; }
+  bool Destroyed() const { return mDidPreDestroy; }
+
   nsIDocument* GetDocument() const { return mDocument; }
+
   nsIPresShell* GetPresShell() const
   {
     return mDocument ? mDocument->GetShell() : nullptr;
   }
   nsPresContext* GetPresContext() const
   {
     nsIPresShell* presShell = GetPresShell();
     return presShell ? presShell->GetPresContext() : nullptr;
   }
+
   already_AddRefed<nsIWidget> GetWidget();
+
   nsISelectionController* GetSelectionController() const
   {
     if (mSelectionController) {
       return mSelectionController;
     }
     if (!mDocument) {
       return nullptr;
     }
     nsIPresShell* presShell = mDocument->GetShell();
     if (!presShell) {
       return nullptr;
     }
     nsISelectionController* sc = static_cast<PresShell*>(presShell);
     return sc;
   }
+
+  nsresult GetSelection(SelectionType aSelectionType,
+                        Selection** aSelection);
+
+  Selection* GetSelection(SelectionType aSelectionType =
+                                          SelectionType::eNormal)
+  {
+    nsISelectionController* sc = GetSelectionController();
+    if (!sc) {
+      return nullptr;
+    }
+    Selection* selection = sc->GetSelection(ToRawSelectionType(aSelectionType));
+    return selection;
+  }
+
+  /**
+   * Fast non-refcounting editor root element accessor
+   */
+  Element* GetRoot() const { return mRootElement; }
+
+  RangeUpdater& RangeUpdaterRef() { return mRangeUpdater; }
+
   enum NotificationForEditorObservers
   {
     eNotifyEditorObserversOfEnd,
     eNotifyEditorObserversOfBefore,
     eNotifyEditorObserversOfCancel
   };
   void NotifyEditorObservers(NotificationForEditorObservers aNotification);
 
-  // nsIEditor methods
-  NS_DECL_NSIEDITOR
-
-  // nsISelectionListener method
-  NS_DECL_NSISELECTIONLISTENER
-
   /**
    * Set or unset TextInputListener.  If setting non-nullptr when the editor
    * already has a TextInputListener, this will crash in debug build.
    */
   void SetTextInputListener(TextInputListener* aTextInputListener);
 
   /**
    * Set or unset IMEContentObserver.  If setting non-nullptr when the editor
    * already has an IMEContentObserver, this will crash in debug build.
    */
   void SetIMEContentObserver(IMEContentObserver* aIMEContentObserver);
 
-public:
   virtual bool IsModifiableNode(nsINode* aNode);
 
   /**
+   * Returns current composition.
+   */
+  TextComposition* GetComposition() const;
+
+  /**
+   * Get preferred IME status of current widget.
+   */
+  virtual nsresult GetPreferredIMEState(widget::IMEState* aState);
+
+  /**
+   * Returns true if there is composition string and not fixed.
+   */
+  bool IsIMEComposing() const;
+
+  /**
+   * Commit composition if there is.
+   * Note that when there is a composition, this requests to commit composition
+   * to native IME.  Therefore, when there is composition, this can do anything.
+   * For example, the editor instance, the widget or the process itself may
+   * be destroyed.
+   */
+  nsresult CommitComposition();
+
+  void SwitchTextDirectionTo(uint32_t aDirection);
+
+  /**
+   * Finalizes selection and caret for the editor.
+   */
+  nsresult FinalizeSelection();
+
+  /**
+   * Returns true if selection is in an editable element and both the range
+   * start and the range end are editable.  E.g., even if the selection range
+   * includes non-editable elements, returns true when one of common ancestors
+   * of the range start and the range end is editable.  Otherwise, false.
+   */
+  bool IsSelectionEditable();
+
+  /**
+   * Returns number of undo or redo items.
+   */
+  size_t NumberOfUndoItems() const
+  {
+    return mTransactionManager ? mTransactionManager->NumberOfUndoItems() : 0;
+  }
+  size_t NumberOfRedoItems() const
+  {
+    return mTransactionManager ? mTransactionManager->NumberOfRedoItems() : 0;
+  }
+
+  /**
+   * Returns true if this editor can store transactions for undo/redo.
+   */
+  bool IsUndoRedoEnabled() const
+  {
+    return !!mTransactionManager;
+  }
+
+  /**
+   * Return true if it's possible to undo/redo right now.
+   */
+  bool CanUndo() const
+  {
+    return IsUndoRedoEnabled() && NumberOfUndoItems() > 0;
+  }
+  bool CanRedo() const
+  {
+    return IsUndoRedoEnabled() && NumberOfRedoItems() > 0;
+  }
+
+  /**
+   * Enables or disables undo/redo feature.  Returns true if it succeeded,
+   * otherwise, e.g., we're undoing or redoing, returns false.
+   */
+  bool EnableUndoRedo(int32_t aMaxTransactionCount = -1)
+  {
+    if (!mTransactionManager) {
+      mTransactionManager = new TransactionManager();
+    }
+    return mTransactionManager->EnableUndoRedo(aMaxTransactionCount);
+  }
+  bool DisableUndoRedo()
+  {
+    if (!mTransactionManager) {
+      return true;
+    }
+    // XXX Even we clear the transaction manager, IsUndoRedoEnabled() keep
+    //     returning true...
+    return mTransactionManager->DisableUndoRedo();
+  }
+  bool ClearUndoRedo()
+  {
+    if (!mTransactionManager) {
+      return true;
+    }
+    return mTransactionManager->ClearUndoRedo();
+  }
+
+  /**
+   * Adds or removes transaction listener to or from the transaction manager.
+   * Note that TransactionManager does not check if the listener is in the
+   * array.  So, caller of AddTransactionListener() needs to manage if it's
+   * already been registered to the transaction manager.
+   */
+  bool AddTransactionListener(nsITransactionListener& aListener)
+  {
+    if (!mTransactionManager) {
+      return false;
+    }
+    return mTransactionManager->AddTransactionListener(aListener);
+  }
+  bool RemoveTransactionListener(nsITransactionListener& aListener)
+  {
+    if (!mTransactionManager) {
+      return false;
+    }
+    return mTransactionManager->RemoveTransactionListener(aListener);
+  }
+
+  virtual nsresult HandleKeyPressEvent(WidgetKeyboardEvent* aKeyboardEvent);
+
+  virtual dom::EventTarget* GetDOMEventTarget() = 0;
+
+  /**
+   * Accessor methods to flags.
+   */
+  uint32_t Flags() const { return mFlags; }
+
+  nsresult AddFlags(uint32_t aFlags)
+  {
+    const uint32_t kOldFlags = Flags();
+    const uint32_t kNewFlags = (kOldFlags | aFlags);
+    if (kNewFlags == kOldFlags) {
+      return NS_OK;
+    }
+    return SetFlags(kNewFlags); // virtual call and may be expensive.
+  }
+  nsresult RemoveFlags(uint32_t aFlags)
+  {
+    const uint32_t kOldFlags = Flags();
+    const uint32_t kNewFlags = (kOldFlags & ~aFlags);
+    if (kNewFlags == kOldFlags) {
+      return NS_OK;
+    }
+    return SetFlags(kNewFlags); // virtual call and may be expensive.
+  }
+  nsresult AddAndRemoveFlags(uint32_t aAddingFlags, uint32_t aRemovingFlags)
+  {
+    MOZ_ASSERT(!(aAddingFlags & aRemovingFlags),
+               "Same flags are specified both adding and removing");
+    const uint32_t kOldFlags = Flags();
+    const uint32_t kNewFlags = ((kOldFlags | aAddingFlags) & ~aRemovingFlags);
+    if (kNewFlags == kOldFlags) {
+      return NS_OK;
+    }
+    return SetFlags(kNewFlags); // virtual call and may be expensive.
+  }
+
+  bool IsPlaintextEditor() const
+  {
+    return (mFlags & nsIPlaintextEditor::eEditorPlaintextMask) != 0;
+  }
+
+  bool IsSingleLineEditor() const
+  {
+    return (mFlags & nsIPlaintextEditor::eEditorSingleLineMask) != 0;
+  }
+
+  bool IsPasswordEditor() const
+  {
+    return (mFlags & nsIPlaintextEditor::eEditorPasswordMask) != 0;
+  }
+
+  // FYI: Both IsRightToLeft() and IsLeftToRight() may return false if
+  //      the editor inherits the content node's direction.
+  bool IsRightToLeft() const
+  {
+    return (mFlags & nsIPlaintextEditor::eEditorRightToLeft) != 0;
+  }
+  bool IsLeftToRight() const
+  {
+    return (mFlags & nsIPlaintextEditor::eEditorLeftToRight) != 0;
+  }
+
+  bool IsReadonly() const
+  {
+    return (mFlags & nsIPlaintextEditor::eEditorReadonlyMask) != 0;
+  }
+
+  bool IsDisabled() const
+  {
+    return (mFlags & nsIPlaintextEditor::eEditorDisabledMask) != 0;
+  }
+
+  bool IsInputFiltered() const
+  {
+    return (mFlags & nsIPlaintextEditor::eEditorFilterInputMask) != 0;
+  }
+
+  bool IsMailEditor() const
+  {
+    return (mFlags & nsIPlaintextEditor::eEditorMailMask) != 0;
+  }
+
+  bool IsWrapHackEnabled() const
+  {
+    return (mFlags & nsIPlaintextEditor::eEditorEnableWrapHackMask) != 0;
+  }
+
+  bool IsFormWidget() const
+  {
+    return (mFlags & nsIPlaintextEditor::eEditorWidgetMask) != 0;
+  }
+
+  bool NoCSS() const
+  {
+    return (mFlags & nsIPlaintextEditor::eEditorNoCSSMask) != 0;
+  }
+
+  bool IsInteractionAllowed() const
+  {
+    return (mFlags & nsIPlaintextEditor::eEditorAllowInteraction) != 0;
+  }
+
+  bool DontEchoPassword() const
+  {
+    return (mFlags & nsIPlaintextEditor::eEditorDontEchoPassword) != 0;
+  }
+
+  bool ShouldSkipSpellCheck() const
+  {
+    return (mFlags & nsIPlaintextEditor::eEditorSkipSpellCheck) != 0;
+  }
+
+  bool IsTabbable() const
+  {
+    return IsSingleLineEditor() || IsPasswordEditor() || IsFormWidget() ||
+           IsInteractionAllowed();
+  }
+
+  bool HasIndependentSelection() const
+  {
+    return !!mSelectionController;
+  }
+
+  bool IsModifiable() const
+  {
+    return !IsReadonly();
+  }
+
+  /**
+   * IsInEditAction() return true while the instance is handling an edit action.
+   * Otherwise, false.
+   */
+  bool IsInEditAction() const { return mIsInEditAction; }
+
+  /**
+   * SuppressDispatchingInputEvent() suppresses or unsuppresses dispatching
+   * "input" event.
+   */
+  void SuppressDispatchingInputEvent(bool aSuppress)
+  {
+    mDispatchInputEvent = !aSuppress;
+  }
+
+  /**
+   * IsSuppressingDispatchingInputEvent() returns true if the editor stops
+   * dispatching input event.  Otherwise, false.
+   */
+  bool IsSuppressingDispatchingInputEvent() const
+  {
+    return !mDispatchInputEvent;
+  }
+
+  /**
+   * Returns true if markNodeDirty() has any effect.  Returns false if
+   * markNodeDirty() is a no-op.
+   */
+  bool OutputsMozDirty() const
+  {
+    // Return true for Composer (!IsInteractionAllowed()) or mail
+    // (IsMailEditor()), but false for webpages.
+    return !IsInteractionAllowed() || IsMailEditor();
+  }
+
+  /**
+   * Get the input event target. This might return null.
+   */
+  virtual already_AddRefed<nsIContent> GetInputEventTargetContent() = 0;
+
+  /**
+   * Get the focused content, if we're focused.  Returns null otherwise.
+   */
+  virtual nsIContent* GetFocusedContent();
+
+  /**
+   * Get the focused content for the argument of some IMEStateManager's
+   * methods.
+   */
+  virtual already_AddRefed<nsIContent> GetFocusedContentForIME();
+
+  /**
+   * Whether the aGUIEvent should be handled by this editor or not.  When this
+   * returns false, The aGUIEvent shouldn't be handled on this editor,
+   * i.e., The aGUIEvent should be handled by another inner editor or ancestor
+   * elements.
+   */
+  virtual bool IsAcceptableInputEvent(WidgetGUIEvent* aGUIEvent);
+
+  /**
+   * FindSelectionRoot() returns a selection root of this editor when aNode
+   * gets focus.  aNode must be a content node or a document node.  When the
+   * target isn't a part of this editor, returns nullptr.  If this is for
+   * designMode, you should set the document node to aNode except that an
+   * element in the document has focus.
+   */
+  virtual already_AddRefed<nsIContent> FindSelectionRoot(nsINode* aNode);
+
+  /**
+   * This method has to be called by EditorEventListener::Focus.
+   * All actions that have to be done when the editor is focused needs to be
+   * added here.
+   */
+  void OnFocus(dom::EventTarget* aFocusEventTarget);
+
+  virtual nsresult InsertFromDrop(dom::DragEvent* aDropEvent) = 0;
+
+  /** Resyncs spellchecking state (enabled/disabled).  This should be called
+    * when anything that affects spellchecking state changes, such as the
+    * spellcheck attribute value.
+    */
+  void SyncRealTimeSpell();
+
+protected: // May be called by friends.
+  /**
    * InsertTextWithTransaction() inserts aStringToInsert to aPointToInsert or
    * better insertion point around it.  If aPointToInsert isn't in a text node,
    * this method looks for the nearest point in a text node with
    * FindBetterInsertionPoint().  If there is no text node, this creates
    * new text node and put aStringToInsert to it.
    *
    * @param aDocument       The document of this editor.
    * @param aStringToInsert The string to insert.
@@ -651,43 +1018,16 @@ public:
 
   /**
    * Creates text node which is marked as "maybe modified frequently".
    */
   static already_AddRefed<nsTextNode> CreateTextNode(nsIDocument& aDocument,
                                                      const nsAString& aData);
 
   /**
-   * Get preferred IME status of current widget.
-   */
-  virtual nsresult GetPreferredIMEState(widget::IMEState* aState);
-
-  /**
-   * Commit composition if there is.
-   * Note that when there is a composition, this requests to commit composition
-   * to native IME.  Therefore, when there is composition, this can do anything.
-   * For example, the editor instance, the widget or the process itself may
-   * be destroyed.
-   */
-  nsresult CommitComposition();
-
-  void SwitchTextDirectionTo(uint32_t aDirection);
-
-  RangeUpdater& RangeUpdaterRef() { return mRangeUpdater; }
-
-  /**
-   * Finalizes selection and caret for the editor.
-   */
-  nsresult FinalizeSelection();
-
-protected:
-  nsresult DetermineCurrentDirection();
-  void FireInputEvent();
-
-  /**
    * Create an element node whose name is aTag at before aPointToInsert.  When
    * this succeed to create an element node, this sets aPointToInsert to the
    * new element because the relation of child and offset may be broken.
    * If the caller needs to collapse the selection to next to the new element
    * node, it should call |aPointToInsert.AdvanceOffset()| after calling this.
    *
    * @param aTag            The element name to create.
    * @param aPointToInsert  The insertion point of new element.  If this refers
@@ -794,218 +1134,16 @@ protected:
    */
   already_AddRefed<Element>
   InsertContainerWithTransactionInternal(nsIContent& aContent,
                                          nsAtom& aTagName,
                                          nsAtom& aAttribute,
                                          const nsAString& aAttributeValue);
 
   /**
-   * Called after a transaction is done successfully.
-   */
-  void DoAfterDoTransaction(nsITransaction *aTxn);
-
-  /**
-   * Called after a transaction is undone successfully.
-   */
-
-  void DoAfterUndoTransaction();
-
-  /**
-   * Called after a transaction is redone successfully.
-   */
-  void DoAfterRedoTransaction();
-
-  // Note that aSelection is optional and can be nullptr.
-  nsresult DoTransaction(Selection* aSelection,
-                         nsITransaction* aTxn);
-
-  enum TDocumentListenerNotification
-  {
-    eDocumentCreated,
-    eDocumentToBeDestroyed,
-    eDocumentStateChanged
-  };
-
-  /**
-   * Tell the doc state listeners that the doc state has changed.
-   */
-  nsresult NotifyDocumentListeners(
-             TDocumentListenerNotification aNotificationType);
-
-  /**
-   * Make the given selection span the entire document.
-   */
-  virtual nsresult SelectEntireDocument(Selection* aSelection);
-
-  /**
-   * Helper method for scrolling the selection into view after
-   * an edit operation. aScrollToAnchor should be true if you
-   * want to scroll to the point where the selection was started.
-   * If false, it attempts to scroll the end of the selection into view.
-   *
-   * Editor methods *should* call this method instead of the versions
-   * in the various selection interfaces, since this version makes sure
-   * that the editor's sync/async settings for reflowing, painting, and
-   * scrolling match.
-   */
-  nsresult ScrollSelectionIntoView(bool aScrollToAnchor);
-
-  virtual bool IsBlockNode(nsINode* aNode);
-
-  /**
-   * Helper for GetPreviousNodeInternal() and GetNextNodeInternal().
-   */
-  nsIContent* FindNextLeafNode(nsINode* aCurrentNode,
-                               bool aGoForward,
-                               bool bNoBlockCrossing);
-  nsIContent* FindNode(nsINode* aCurrentNode,
-                       bool aGoForward,
-                       bool aEditableNode,
-                       bool aFindAnyDataNode,
-                       bool bNoBlockCrossing);
-
-  /**
-   * Get the node immediately previous node of aNode.
-   * @param atNode               The node from which we start the search.
-   * @param aFindEditableNode    If true, only return an editable node.
-   * @param aFindAnyDataNode     If true, may return invisible data node
-   *                             like Comment.
-   * @param aNoBlockCrossing     If true, don't move across "block" nodes,
-   *                             whatever that means.
-   * @return                     The node that occurs before aNode in
-   *                             the tree, skipping non-editable nodes if
-   *                             aFindEditableNode is true.  If there is no
-   *                             previous node, returns nullptr.
-   */
-  nsIContent* GetPreviousNodeInternal(nsINode& aNode,
-                                      bool aFindEditableNode,
-                                      bool aFindAnyDataNode,
-                                      bool aNoBlockCrossing);
-
-  /**
-   * And another version that takes a point in DOM tree rather than a node.
-   */
-  nsIContent* GetPreviousNodeInternal(const EditorRawDOMPoint& aPoint,
-                                      bool aFindEditableNode,
-                                      bool aFindAnyDataNode,
-                                      bool aNoBlockCrossing);
-
-  /**
-   * Get the node immediately next node of aNode.
-   * @param aNode                The node from which we start the search.
-   * @param aFindEditableNode    If true, only return an editable node.
-   * @param aFindAnyDataNode     If true, may return invisible data node
-   *                             like Comment.
-   * @param aNoBlockCrossing     If true, don't move across "block" nodes,
-   *                             whatever that means.
-   * @return                     The node that occurs after aNode in the
-   *                             tree, skipping non-editable nodes if
-   *                             aFindEditableNode is true.  If there is no
-   *                             next node, returns nullptr.
-   */
-  nsIContent* GetNextNodeInternal(nsINode& aNode,
-                                  bool aFindEditableNode,
-                                  bool aFindAnyDataNode,
-                                  bool bNoBlockCrossing);
-
-  /**
-   * And another version that takes a point in DOM tree rather than a node.
-   */
-  nsIContent* GetNextNodeInternal(const EditorRawDOMPoint& aPoint,
-                                  bool aFindEditableNode,
-                                  bool aFindAnyDataNode,
-                                  bool aNoBlockCrossing);
-
-
-  virtual nsresult InstallEventListeners();
-  virtual void CreateEventListeners();
-  virtual void RemoveEventListeners();
-
-  /**
-   * Return true if spellchecking should be enabled for this editor.
-   */
-  bool GetDesiredSpellCheckState();
-
-  bool CanEnableSpellCheck()
-  {
-    // Check for password/readonly/disabled, which are not spellchecked
-    // regardless of DOM. Also, check to see if spell check should be skipped
-    // or not.
-    return !IsPasswordEditor() && !IsReadonly() && !IsDisabled() &&
-           !ShouldSkipSpellCheck();
-  }
-
-  nsresult GetSelection(SelectionType aSelectionType,
-                        Selection** aSelection);
-
-  /**
-   * (Begin|End)PlaceholderTransaction() are called by AutoPlaceholderBatch.
-   * This set of methods are similar to the (Begin|End)Transaction(), but do
-   * not use the transaction managers batching feature.  Instead we use a
-   * placeholder transaction to wrap up any further transaction while the
-   * batch is open.  The advantage of this is that placeholder transactions
-   * can later merge, if needed.  Merging is unavailable between transaction
-   * manager batches.
-   */
-  void BeginPlaceholderTransaction(nsAtom* aTransactionName);
-  void EndPlaceholderTransaction();
-
-  /**
-   * InitializeSelectionAncestorLimit() is called by InitializeSelection().
-   * When this is called, each implementation has to call
-   * aSelection.SetAncestorLimiter() with aAnotherLimit.
-   *
-   * @param aSelection          The selection.
-   * @param aAncestorLimit      New ancestor limit of aSelection.  This always
-   *                            has parent node.  So, it's always safe to
-   *                            call SetAncestorLimit() with this node.
-   */
-  virtual void InitializeSelectionAncestorLimit(Selection& aSelection,
-                                                nsIContent& aAncestorLimit);
-
-public:
-  /**
-   * PostCreate should be called after Init, and is the time that the editor
-   * tells its documentStateObservers that the document has been created.
-   */
-  nsresult PostCreate();
-
- /**
-   * PreDestroy is called before the editor goes away, and gives the editor a
-   * chance to tell its documentStateObservers that the document is going away.
-   * @param aDestroyingFrames set to true when the frames being edited
-   * are being destroyed (so there is no need to modify any nsISelections,
-   * nor is it safe to do so)
-   */
-  virtual void PreDestroy(bool aDestroyingFrames);
-
-  /**
-   * All editor operations which alter the doc should be prefaced
-   * with a call to StartOperation, naming the action and direction.
-   */
-  virtual nsresult StartOperation(EditAction opID,
-                                  nsIEditor::EDirection aDirection);
-
-  /**
-   * All editor operations which alter the doc should be followed
-   * with a call to 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);
-  void StopPreservingSelection();
-
-  /**
    * DoSplitNode() creates a new node (left node) identical to an existing
    * node (right node), and split the contents between the same point in both
    * nodes.
    *
    * @param aStartOfRightNode   The point to split.  Its container will be
    *                            the right node, i.e., become the new node's
    *                            next sibling.  And the point will be start
    *                            of the right node.
@@ -1035,26 +1173,61 @@ public:
    *                      same type.
    * @param aParent       The parent of aNodeToKeep
    */
   nsresult DoJoinNodes(nsINode* aNodeToKeep,
                        nsINode* aNodeToJoin,
                        nsINode* aParent);
 
   /**
-   * Return the offset of aChild in aParent.  Asserts fatally if parent or
-   * child is null, or parent is not child's parent.
-   * FYI: aChild must not be being removed from aParent.  In such case, these
-   *      methods may return wrong index if aChild doesn't have previous
-   *      sibling or next sibling.
+   * SplitNodeDeepWithTransaction() splits aMostAncestorToSplit deeply.
+   *
+   * @param aMostAncestorToSplit        The most ancestor node which should be
+   *                                    split.
+   * @param aStartOfDeepestRightNode    The start point of deepest right node.
+   *                                    This point must be descendant of
+   *                                    aMostAncestorToSplit.
+   * @param aSplitAtEdges               Whether the caller allows this to
+   *                                    create empty container element when
+   *                                    split point is start or end of an
+   *                                    element.
+   * @return                            SplitPoint() returns split point in
+   *                                    aMostAncestorToSplit.  The point must
+   *                                    be good to insert something if the
+   *                                    caller want to do it.
    */
-  static int32_t GetChildOffset(nsIDOMNode* aChild,
-                                nsIDOMNode* aParent);
-  static int32_t GetChildOffset(nsINode* aChild,
-                                nsINode* aParent);
+  template<typename PT, typename CT>
+  SplitNodeResult
+  SplitNodeDeepWithTransaction(
+    nsIContent& aMostAncestorToSplit,
+    const EditorDOMPointBase<PT, CT>& aDeepestStartOfRightNode,
+    SplitAtEdges aSplitAtEdges);
+
+  /**
+   * JoinNodesDeepWithTransaction() joins aLeftNode and aRightNode "deeply".
+   * First, they are joined simply, then, new right node is assumed as the
+   * child at length of the left node before joined and new left node is
+   * assumed as its previous sibling.  Then, they will be joined again.
+   * And then, these steps are repeated.
+   *
+   * @param aLeftNode   The node which will be removed form the tree.
+   * @param aRightNode  The node which will be inserted the contents of
+   *                    aLeftNode.
+   * @return            The point of the first child of the last right node.
+   */
+  EditorDOMPoint JoinNodesDeepWithTransaction(nsIContent& aLeftNode,
+                                              nsIContent& aRightNode);
+
+  /**
+   * Note that aSelection is optional and can be nullptr.
+   */
+  nsresult DoTransaction(Selection* aSelection,
+                         nsITransaction* aTxn);
+
+  virtual bool IsBlockNode(nsINode* aNode);
 
   /**
    * Set outOffset to the offset of aChild in the parent.
    * Returns the parent of aChild.
    */
   static nsINode* GetNodeLocation(nsINode* aChild, int32_t* aOffset);
 
   /**
@@ -1278,24 +1451,16 @@ public:
     if (!aNode.IsContent() || IsMozEditorBogusNode(&aNode)) {
       return false;
     }
     return aNode.NodeType() == nsINode::ELEMENT_NODE ||
            aNode.NodeType() == nsINode::TEXT_NODE;
   }
 
   /**
-   * Returns true if selection is in an editable element and both the range
-   * start and the range end are editable.  E.g., even if the selection range
-   * includes non-editable elements, returns true when one of common ancestors
-   * of the range start and the range end is editable.  Otherwise, false.
-   */
-  bool IsSelectionEditable();
-
-  /**
    * Returns true if aNode is a MozEditorBogus node.
    */
   bool IsMozEditorBogusNode(const nsINode* aNode) const
   {
     return aNode && aNode->IsElement() &&
            aNode->AsElement()->AttrValueIs(kNameSpaceID_None,
                kMOZEditorBogusNodeAttrAtom, kMOZEditorBogusNodeValue,
                eCaseMatters);
@@ -1307,112 +1472,21 @@ public:
   uint32_t CountEditableChildren(nsINode* aNode);
 
   /**
    * Find the deep first and last children.
    */
   nsINode* GetFirstEditableNode(nsINode* aRoot);
 
   /**
-   * Returns current composition.
-   */
-  TextComposition* GetComposition() const;
-
-  /**
-   * Returns true if there is composition string and not fixed.
-   */
-  bool IsIMEComposing() const;
-
-  /**
    * Returns true when inserting text should be a part of current composition.
    */
   bool ShouldHandleIMEComposition() const;
 
   /**
-   * Returns number of undo or redo items.
-   */
-  size_t NumberOfUndoItems() const
-  {
-    return mTransactionManager ? mTransactionManager->NumberOfUndoItems() : 0;
-  }
-  size_t NumberOfRedoItems() const
-  {
-    return mTransactionManager ? mTransactionManager->NumberOfRedoItems() : 0;
-  }
-
-  /**
-   * Returns true if this editor can store transactions for undo/redo.
-   */
-  bool IsUndoRedoEnabled() const
-  {
-    return !!mTransactionManager;
-  }
-
-  /**
-   * Return true if it's possible to undo/redo right now.
-   */
-  bool CanUndo() const
-  {
-    return IsUndoRedoEnabled() && NumberOfUndoItems() > 0;
-  }
-  bool CanRedo() const
-  {
-    return IsUndoRedoEnabled() && NumberOfRedoItems() > 0;
-  }
-
-  /**
-   * Enables or disables undo/redo feature.  Returns true if it succeeded,
-   * otherwise, e.g., we're undoing or redoing, returns false.
-   */
-  bool EnableUndoRedo(int32_t aMaxTransactionCount = -1)
-  {
-    if (!mTransactionManager) {
-      mTransactionManager = new TransactionManager();
-    }
-    return mTransactionManager->EnableUndoRedo(aMaxTransactionCount);
-  }
-  bool DisableUndoRedo()
-  {
-    if (!mTransactionManager) {
-      return true;
-    }
-    // XXX Even we clear the transaction manager, IsUndoRedoEnabled() keep
-    //     returning true...
-    return mTransactionManager->DisableUndoRedo();
-  }
-  bool ClearUndoRedo()
-  {
-    if (!mTransactionManager) {
-      return true;
-    }
-    return mTransactionManager->ClearUndoRedo();
-  }
-
-  /**
-   * Adds or removes transaction listener to or from the transaction manager.
-   * Note that TransactionManager does not check if the listener is in the
-   * array.  So, caller of AddTransactionListener() needs to manage if it's
-   * already been registered to the transaction manager.
-   */
-  bool AddTransactionListener(nsITransactionListener& aListener)
-  {
-    if (!mTransactionManager) {
-      return false;
-    }
-    return mTransactionManager->AddTransactionListener(aListener);
-  }
-  bool RemoveTransactionListener(nsITransactionListener& aListener)
-  {
-    if (!mTransactionManager) {
-      return false;
-    }
-    return mTransactionManager->RemoveTransactionListener(aListener);
-  }
-
-  /**
    * From html rules code - migration in progress.
    */
   static nsAtom* GetTag(nsIDOMNode* aNode);
 
   virtual bool AreNodesSameType(nsIContent* aNode1, nsIContent* aNode2);
 
   static bool IsTextNode(nsIDOMNode* aNode);
   static bool IsTextNode(nsINode* aNode)
@@ -1433,366 +1507,62 @@ public:
   static nsIContent* GetNodeAtRangeOffsetPoint(const RawRangeBoundary& aPoint);
 
   static EditorRawDOMPoint GetStartPoint(Selection* aSelection);
   static EditorRawDOMPoint GetEndPoint(Selection* aSelection);
 
   static nsresult GetEndChildNode(Selection* aSelection,
                                   nsIContent** aEndNode);
 
-  Selection* GetSelection(SelectionType aSelectionType =
-                                          SelectionType::eNormal)
-  {
-    nsISelectionController* sc = GetSelectionController();
-    if (!sc) {
-      return nullptr;
-    }
-    Selection* selection = sc->GetSelection(ToRawSelectionType(aSelectionType));
-    return selection;
-  }
-
   /**
    * CollapseSelectionToEnd() collapses the selection to the end of the editor.
    */
   nsresult CollapseSelectionToEnd(Selection* aSelection);
 
   /**
    * Helpers to add a node to the selection.
    * Used by table cell selection methods.
    */
   nsresult CreateRange(nsINode* aStartContainer, int32_t aStartOffset,
                        nsINode* aEndContainer, int32_t aEndOffset,
                        nsRange** aRange);
 
-  /**
-   * Creates a range with just the supplied node and appends that to the
-   * selection.
-   */
-  nsresult AppendNodeToSelectionAsRange(nsINode* aNode);
-
-  /**
-   * When you are using AppendNodeToSelectionAsRange(), call this first to
-   * start a new selection.
-   */
-  nsresult ClearSelection();
-
   static bool IsPreformatted(nsINode* aNode);
 
-  /**
-   * SplitNodeDeepWithTransaction() splits aMostAncestorToSplit deeply.
-   *
-   * @param aMostAncestorToSplit        The most ancestor node which should be
-   *                                    split.
-   * @param aStartOfDeepestRightNode    The start point of deepest right node.
-   *                                    This point must be descendant of
-   *                                    aMostAncestorToSplit.
-   * @param aSplitAtEdges               Whether the caller allows this to
-   *                                    create empty container element when
-   *                                    split point is start or end of an
-   *                                    element.
-   * @return                            SplitPoint() returns split point in
-   *                                    aMostAncestorToSplit.  The point must
-   *                                    be good to insert something if the
-   *                                    caller want to do it.
-   */
-  template<typename PT, typename CT>
-  SplitNodeResult
-  SplitNodeDeepWithTransaction(
-    nsIContent& aMostAncestorToSplit,
-    const EditorDOMPointBase<PT, CT>& aDeepestStartOfRightNode,
-    SplitAtEdges aSplitAtEdges);
-
-  /**
-   * JoinNodesDeepWithTransaction() joins aLeftNode and aRightNode "deeply".
-   * First, they are joined simply, then, new right node is assumed as the
-   * child at length of the left node before joined and new left node is
-   * assumed as its previous sibling.  Then, they will be joined again.
-   * And then, these steps are repeated.
-   *
-   * @param aLeftNode   The node which will be removed form the tree.
-   * @param aRightNode  The node which will be inserted the contents of
-   *                    aLeftNode.
-   * @return            The point of the first child of the last right node.
-   */
-  EditorDOMPoint JoinNodesDeepWithTransaction(nsIContent& aLeftNode,
-                                              nsIContent& aRightNode);
-
-  nsresult GetString(const nsAString& name, nsAString& value);
-
-  void BeginUpdateViewBatch();
-  virtual nsresult EndUpdateViewBatch();
-
   bool GetShouldTxnSetSelection();
 
-  virtual nsresult HandleKeyPressEvent(WidgetKeyboardEvent* aKeyboardEvent);
-
   nsresult HandleInlineSpellCheck(EditAction action,
                                   Selection& aSelection,
                                   nsINode* previousSelectedNode,
                                   uint32_t previousSelectedOffset,
                                   nsINode* aStartContainer,
                                   uint32_t aStartOffset,
                                   nsINode* aEndContainer,
                                   uint32_t aEndOffset);
 
-  virtual dom::EventTarget* GetDOMEventTarget() = 0;
-
-  /**
-   * Fast non-refcounting editor root element accessor
-   */
-  Element* GetRoot() const { return mRootElement; }
-
   /**
    * Likewise, but gets the editor's root instead, which is different for HTML
    * editors.
    */
   virtual Element* GetEditorRoot();
 
   /**
    * Likewise, but gets the text control element instead of the root for
    * plaintext editors.
    */
   Element* GetExposedRoot();
 
   /**
-   * Accessor methods to flags.
-   */
-  uint32_t Flags() const { return mFlags; }
-
-  nsresult AddFlags(uint32_t aFlags)
-  {
-    const uint32_t kOldFlags = Flags();
-    const uint32_t kNewFlags = (kOldFlags | aFlags);
-    if (kNewFlags == kOldFlags) {
-      return NS_OK;
-    }
-    return SetFlags(kNewFlags); // virtual call and may be expensive.
-  }
-  nsresult RemoveFlags(uint32_t aFlags)
-  {
-    const uint32_t kOldFlags = Flags();
-    const uint32_t kNewFlags = (kOldFlags & ~aFlags);
-    if (kNewFlags == kOldFlags) {
-      return NS_OK;
-    }
-    return SetFlags(kNewFlags); // virtual call and may be expensive.
-  }
-  nsresult AddAndRemoveFlags(uint32_t aAddingFlags, uint32_t aRemovingFlags)
-  {
-    MOZ_ASSERT(!(aAddingFlags & aRemovingFlags),
-               "Same flags are specified both adding and removing");
-    const uint32_t kOldFlags = Flags();
-    const uint32_t kNewFlags = ((kOldFlags | aAddingFlags) & ~aRemovingFlags);
-    if (kNewFlags == kOldFlags) {
-      return NS_OK;
-    }
-    return SetFlags(kNewFlags); // virtual call and may be expensive.
-  }
-
-  bool IsPlaintextEditor() const
-  {
-    return (mFlags & nsIPlaintextEditor::eEditorPlaintextMask) != 0;
-  }
-
-  bool IsSingleLineEditor() const
-  {
-    return (mFlags & nsIPlaintextEditor::eEditorSingleLineMask) != 0;
-  }
-
-  bool IsPasswordEditor() const
-  {
-    return (mFlags & nsIPlaintextEditor::eEditorPasswordMask) != 0;
-  }
-
-  // FYI: Both IsRightToLeft() and IsLeftToRight() may return false if
-  //      the editor inherits the content node's direction.
-  bool IsRightToLeft() const
-  {
-    return (mFlags & nsIPlaintextEditor::eEditorRightToLeft) != 0;
-  }
-  bool IsLeftToRight() const
-  {
-    return (mFlags & nsIPlaintextEditor::eEditorLeftToRight) != 0;
-  }
-
-  bool IsReadonly() const
-  {
-    return (mFlags & nsIPlaintextEditor::eEditorReadonlyMask) != 0;
-  }
-
-  bool IsDisabled() const
-  {
-    return (mFlags & nsIPlaintextEditor::eEditorDisabledMask) != 0;
-  }
-
-  bool IsInputFiltered() const
-  {
-    return (mFlags & nsIPlaintextEditor::eEditorFilterInputMask) != 0;
-  }
-
-  bool IsMailEditor() const
-  {
-    return (mFlags & nsIPlaintextEditor::eEditorMailMask) != 0;
-  }
-
-  bool IsWrapHackEnabled() const
-  {
-    return (mFlags & nsIPlaintextEditor::eEditorEnableWrapHackMask) != 0;
-  }
-
-  bool IsFormWidget() const
-  {
-    return (mFlags & nsIPlaintextEditor::eEditorWidgetMask) != 0;
-  }
-
-  bool NoCSS() const
-  {
-    return (mFlags & nsIPlaintextEditor::eEditorNoCSSMask) != 0;
-  }
-
-  bool IsInteractionAllowed() const
-  {
-    return (mFlags & nsIPlaintextEditor::eEditorAllowInteraction) != 0;
-  }
-
-  bool DontEchoPassword() const
-  {
-    return (mFlags & nsIPlaintextEditor::eEditorDontEchoPassword) != 0;
-  }
-
-  bool ShouldSkipSpellCheck() const
-  {
-    return (mFlags & nsIPlaintextEditor::eEditorSkipSpellCheck) != 0;
-  }
-
-  bool IsTabbable() const
-  {
-    return IsSingleLineEditor() || IsPasswordEditor() || IsFormWidget() ||
-           IsInteractionAllowed();
-  }
-
-  bool HasIndependentSelection() const
-  {
-    return !!mSelectionController;
-  }
-
-  bool IsModifiable() const
-  {
-    return !IsReadonly();
-  }
-
-  /**
-   * IsInEditAction() return true while the instance is handling an edit action.
-   * Otherwise, false.
-   */
-  bool IsInEditAction() const { return mIsInEditAction; }
-
-  /**
-   * SuppressDispatchingInputEvent() suppresses or unsuppresses dispatching
-   * "input" event.
-   */
-  void SuppressDispatchingInputEvent(bool aSuppress)
-  {
-    mDispatchInputEvent = !aSuppress;
-  }
-
-  /**
-   * IsSuppressingDispatchingInputEvent() returns true if the editor stops
-   * dispatching input event.  Otherwise, false.
-   */
-  bool IsSuppressingDispatchingInputEvent() const
-  {
-    return !mDispatchInputEvent;
-  }
-
-  bool Destroyed() const
-  {
-    return mDidPreDestroy;
-  }
-
-  /**
-   * Returns true if markNodeDirty() has any effect.  Returns false if
-   * markNodeDirty() is a no-op.
-   */
-  bool OutputsMozDirty() const
-  {
-    // Return true for Composer (!IsInteractionAllowed()) or mail
-    // (IsMailEditor()), but false for webpages.
-    return !IsInteractionAllowed() || IsMailEditor();
-  }
-
-  /**
-   * Get the input event target. This might return null.
-   */
-  virtual already_AddRefed<nsIContent> GetInputEventTargetContent() = 0;
-
-  /**
-   * Get the focused content, if we're focused.  Returns null otherwise.
-   */
-  virtual nsIContent* GetFocusedContent();
-
-  /**
-   * Get the focused content for the argument of some IMEStateManager's
-   * methods.
-   */
-  virtual already_AddRefed<nsIContent> GetFocusedContentForIME();
-
-  /**
    * Whether the editor is active on the DOM window.  Note that when this
    * returns true but GetFocusedContent() returns null, it means that this editor was
    * focused when the DOM window was active.
    */
   virtual bool IsActiveInDOMWindow();
 
   /**
-   * Whether the aGUIEvent should be handled by this editor or not.  When this
-   * returns false, The aGUIEvent shouldn't be handled on this editor,
-   * i.e., The aGUIEvent should be handled by another inner editor or ancestor
-   * elements.
-   */
-  virtual bool IsAcceptableInputEvent(WidgetGUIEvent* aGUIEvent);
-
-  /**
-   * FindSelectionRoot() returns a selection root of this editor when aNode
-   * gets focus.  aNode must be a content node or a document node.  When the
-   * target isn't a part of this editor, returns nullptr.  If this is for
-   * designMode, you should set the document node to aNode except that an
-   * element in the document has focus.
-   */
-  virtual already_AddRefed<nsIContent> FindSelectionRoot(nsINode* aNode);
-
-  /**
-   * Initializes selection and caret for the editor.  If aEventTarget isn't
-   * a host of the editor, i.e., the editor doesn't get focus, this does
-   * nothing.
-   */
-  nsresult InitializeSelection(dom::EventTarget* aFocusEventTarget);
-
-  /**
-   * This method has to be called by EditorEventListener::Focus.
-   * All actions that have to be done when the editor is focused needs to be
-   * added here.
-   */
-  void OnFocus(dom::EventTarget* aFocusEventTarget);
-
-  /**
-   * Used to insert content from a data transfer into the editable area.
-   * This is called for each item in the data transfer, with the index of
-   * each item passed as aIndex.
-   */
-  virtual nsresult InsertFromDataTransfer(dom::DataTransfer* aDataTransfer,
-                                          int32_t aIndex,
-                                          nsIDocument* aSourceDoc,
-                                          nsINode* aDestinationNode,
-                                          int32_t aDestOffset,
-                                          bool aDoDeleteSelection) = 0;
-
-  virtual nsresult InsertFromDrop(dom::DragEvent* aDropEvent) = 0;
-
-  /**
    * GetIMESelectionStartOffsetIn() returns the start offset of IME selection in
    * the aTextNode.  If there is no IME selection, returns -1.
    */
   int32_t GetIMESelectionStartOffsetIn(nsINode* aTextNode);
 
   /**
    * FindBetterInsertionPoint() tries to look for better insertion point which
    * is typically the nearest text node and offset in it.
@@ -1805,21 +1575,248 @@ public:
 
   /**
    * HideCaret() hides caret with nsCaret::AddForceHide() or may show carent
    * with nsCaret::RemoveForceHide().  This does NOT set visibility of
    * nsCaret.  Therefore, this is stateless.
    */
   void HideCaret(bool aHide);
 
-  /** Resyncs spellchecking state (enabled/disabled).  This should be called
-    * when anything that affects spellchecking state changes, such as the
-    * spellcheck attribute value.
-    */
-  void SyncRealTimeSpell();
+protected: // Called by helper classes.
+  /**
+   * All editor operations which alter the doc should be prefaced
+   * with a call to StartOperation, naming the action and direction.
+   */
+  virtual nsresult StartOperation(EditAction opID,
+                                  nsIEditor::EDirection aDirection);
+
+  /**
+   * All editor operations which alter the doc should be followed
+   * with a call to 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);
+  void StopPreservingSelection();
+
+  /**
+   * (Begin|End)PlaceholderTransaction() are called by AutoPlaceholderBatch.
+   * This set of methods are similar to the (Begin|End)Transaction(), but do
+   * not use the transaction managers batching feature.  Instead we use a
+   * placeholder transaction to wrap up any further transaction while the
+   * batch is open.  The advantage of this is that placeholder transactions
+   * can later merge, if needed.  Merging is unavailable between transaction
+   * manager batches.
+   */
+  void BeginPlaceholderTransaction(nsAtom* aTransactionName);
+  void EndPlaceholderTransaction();
+
+  void BeginUpdateViewBatch();
+  virtual nsresult EndUpdateViewBatch();
+
+protected: // Shouldn't be used by friend classes
+  /**
+   * The default destructor. This should suffice. Should this be pure virtual
+   * for someone to derive from the EditorBase later? I don't believe so.
+   */
+  virtual ~EditorBase();
+
+  nsresult DetermineCurrentDirection();
+  void FireInputEvent();
+
+  /**
+   * Called after a transaction is done successfully.
+   */
+  void DoAfterDoTransaction(nsITransaction *aTxn);
+
+  /**
+   * Called after a transaction is undone successfully.
+   */
+
+  void DoAfterUndoTransaction();
+
+  /**
+   * Called after a transaction is redone successfully.
+   */
+  void DoAfterRedoTransaction();
+
+  /**
+   * Tell the doc state listeners that the doc state has changed.
+   */
+  enum TDocumentListenerNotification
+  {
+    eDocumentCreated,
+    eDocumentToBeDestroyed,
+    eDocumentStateChanged
+  };
+  nsresult NotifyDocumentListeners(
+             TDocumentListenerNotification aNotificationType);
+
+  /**
+   * Make the given selection span the entire document.
+   */
+  virtual nsresult SelectEntireDocument(Selection* aSelection);
+
+  /**
+   * Helper method for scrolling the selection into view after
+   * an edit operation. aScrollToAnchor should be true if you
+   * want to scroll to the point where the selection was started.
+   * If false, it attempts to scroll the end of the selection into view.
+   *
+   * Editor methods *should* call this method instead of the versions
+   * in the various selection interfaces, since this version makes sure
+   * that the editor's sync/async settings for reflowing, painting, and
+   * scrolling match.
+   */
+  nsresult ScrollSelectionIntoView(bool aScrollToAnchor);
+
+  /**
+   * Helper for GetPreviousNodeInternal() and GetNextNodeInternal().
+   */
+  nsIContent* FindNextLeafNode(nsINode* aCurrentNode,
+                               bool aGoForward,
+                               bool bNoBlockCrossing);
+  nsIContent* FindNode(nsINode* aCurrentNode,
+                       bool aGoForward,
+                       bool aEditableNode,
+                       bool aFindAnyDataNode,
+                       bool bNoBlockCrossing);
+
+  /**
+   * Get the node immediately previous node of aNode.
+   * @param atNode               The node from which we start the search.
+   * @param aFindEditableNode    If true, only return an editable node.
+   * @param aFindAnyDataNode     If true, may return invisible data node
+   *                             like Comment.
+   * @param aNoBlockCrossing     If true, don't move across "block" nodes,
+   *                             whatever that means.
+   * @return                     The node that occurs before aNode in
+   *                             the tree, skipping non-editable nodes if
+   *                             aFindEditableNode is true.  If there is no
+   *                             previous node, returns nullptr.
+   */
+  nsIContent* GetPreviousNodeInternal(nsINode& aNode,
+                                      bool aFindEditableNode,
+                                      bool aFindAnyDataNode,
+                                      bool aNoBlockCrossing);
+
+  /**
+   * And another version that takes a point in DOM tree rather than a node.
+   */
+  nsIContent* GetPreviousNodeInternal(const EditorRawDOMPoint& aPoint,
+                                      bool aFindEditableNode,
+                                      bool aFindAnyDataNode,
+                                      bool aNoBlockCrossing);
+
+  /**
+   * Get the node immediately next node of aNode.
+   * @param aNode                The node from which we start the search.
+   * @param aFindEditableNode    If true, only return an editable node.
+   * @param aFindAnyDataNode     If true, may return invisible data node
+   *                             like Comment.
+   * @param aNoBlockCrossing     If true, don't move across "block" nodes,
+   *                             whatever that means.
+   * @return                     The node that occurs after aNode in the
+   *                             tree, skipping non-editable nodes if
+   *                             aFindEditableNode is true.  If there is no
+   *                             next node, returns nullptr.
+   */
+  nsIContent* GetNextNodeInternal(nsINode& aNode,
+                                  bool aFindEditableNode,
+                                  bool aFindAnyDataNode,
+                                  bool bNoBlockCrossing);
+
+  /**
+   * And another version that takes a point in DOM tree rather than a node.
+   */
+  nsIContent* GetNextNodeInternal(const EditorRawDOMPoint& aPoint,
+                                  bool aFindEditableNode,
+                                  bool aFindAnyDataNode,
+                                  bool aNoBlockCrossing);
+
+
+  virtual nsresult InstallEventListeners();
+  virtual void CreateEventListeners();
+  virtual void RemoveEventListeners();
+
+  /**
+   * Return true if spellchecking should be enabled for this editor.
+   */
+  bool GetDesiredSpellCheckState();
+
+  bool CanEnableSpellCheck()
+  {
+    // Check for password/readonly/disabled, which are not spellchecked
+    // regardless of DOM. Also, check to see if spell check should be skipped
+    // or not.
+    return !IsPasswordEditor() && !IsReadonly() && !IsDisabled() &&
+           !ShouldSkipSpellCheck();
+  }
+
+  /**
+   * InitializeSelectionAncestorLimit() is called by InitializeSelection().
+   * When this is called, each implementation has to call
+   * aSelection.SetAncestorLimiter() with aAnotherLimit.
+   *
+   * @param aSelection          The selection.
+   * @param aAncestorLimit      New ancestor limit of aSelection.  This always
+   *                            has parent node.  So, it's always safe to
+   *                            call SetAncestorLimit() with this node.
+   */
+  virtual void InitializeSelectionAncestorLimit(Selection& aSelection,
+                                                nsIContent& aAncestorLimit);
+
+  /**
+   * Return the offset of aChild in aParent.  Asserts fatally if parent or
+   * child is null, or parent is not child's parent.
+   * FYI: aChild must not be being removed from aParent.  In such case, these
+   *      methods may return wrong index if aChild doesn't have previous
+   *      sibling or next sibling.
+   */
+  static int32_t GetChildOffset(nsIDOMNode* aChild,
+                                nsIDOMNode* aParent);
+  static int32_t GetChildOffset(nsINode* aChild,
+                                nsINode* aParent);
+
+  /**
+   * Creates a range with just the supplied node and appends that to the
+   * selection.
+   */
+  nsresult AppendNodeToSelectionAsRange(nsINode* aNode);
+
+  /**
+   * When you are using AppendNodeToSelectionAsRange(), call this first to
+   * start a new selection.
+   */
+  nsresult ClearSelection();
+
+  /**
+   * Initializes selection and caret for the editor.  If aEventTarget isn't
+   * a host of the editor, i.e., the editor doesn't get focus, this does
+   * nothing.
+   */
+  nsresult InitializeSelection(dom::EventTarget* aFocusEventTarget);
+
+  /**
+   * Used to insert content from a data transfer into the editable area.
+   * This is called for each item in the data transfer, with the index of
+   * each item passed as aIndex.
+   */
+  virtual nsresult InsertFromDataTransfer(dom::DataTransfer* aDataTransfer,
+                                          int32_t aIndex,
+                                          nsIDocument* aSourceDoc,
+                                          nsINode* aDestinationNode,
+                                          int32_t aDestOffset,
+                                          bool aDoDeleteSelection) = 0;
 
 private:
   nsCOMPtr<nsISelectionController> mSelectionController;
   nsCOMPtr<nsIDocument> mDocument;
 
 protected:
   enum Tristate
   {
@@ -1905,22 +1902,34 @@ protected:
   bool mIsInEditAction;
   // Whether caret is hidden forcibly.
   bool mHidingCaret;
   // Whether spellchecker dictionary is initialized after focused.
   bool mSpellCheckerDictionaryUpdated;
   // Whether we are an HTML editor class.
   bool mIsHTMLEditorClass;
 
-  friend bool NSCanUnload(nsISupports* serviceMgr);
   friend class AutoPlaceholderBatch;
   friend class AutoRules;
   friend class AutoSelectionRestorer;
   friend class AutoTransactionsConserveSelection;
-  friend class RangeUpdater;
+  friend class AutoUpdateViewBatch;
+  friend class CompositionTransaction;
+  friend class CreateElementTransaction;
+  friend class CSSEditUtils;
+  friend class DeleteTextTransaction;
+  friend class HTMLEditRules;
+  friend class HTMLEditUtils;
+  friend class InsertNodeTransaction;
+  friend class InsertTextTransaction;
+  friend class JoinNodeTransaction;
+  friend class SplitNodeTransaction;
+  friend class TextEditRules;
+  friend class TypeInState;
+  friend class WSRunObject;
   friend class nsIEditor;
 };
 
 } // namespace mozilla
 
 mozilla::EditorBase*
 nsIEditor::AsEditorBase()
 {
--- a/editor/libeditor/HTMLEditor.h
+++ b/editor/libeditor/HTMLEditor.h
@@ -39,20 +39,18 @@ class nsITransferable;
 class nsIClipboard;
 class nsILinkHandler;
 class nsTableWrapperFrame;
 class nsRange;
 
 namespace mozilla {
 class AutoSelectionSetterAfterTableEdit;
 class HTMLEditorEventListener;
-class HTMLEditRules;
 class ResizerSelectionListener;
 class TypeInState;
-class WSRunObject;
 enum class EditAction : int32_t;
 struct PropItem;
 template<class T> class OwningNonNull;
 namespace dom {
 class DocumentFragment;
 class Event;
 class MouseEvent;
 } // namespace dom
@@ -1467,16 +1465,22 @@ protected:
 public:
   friend class AutoSelectionSetterAfterTableEdit;
   friend class HTMLEditorEventListener;
   friend class HTMLEditRules;
   friend class TextEditRules;
   friend class WSRunObject;
 
 private:
+  /**
+   * IsEmptyTextNode() returns true if aNode is a text node and does not have
+   * any visible characters.
+   */
+  bool IsEmptyTextNode(nsINode& aNode);
+
   bool IsSimpleModifiableNode(nsIContent* aContent,
                               nsAtom* aProperty,
                               nsAtom* aAttribute,
                               const nsAString* aValue);
   nsresult SetInlinePropertyOnNodeImpl(nsIContent& aNode,
                                        nsAtom& aProperty,
                                        nsAtom* aAttribute,
                                        const nsAString& aValue);
--- a/editor/libeditor/HTMLStyleEditor.cpp
+++ b/editor/libeditor/HTMLStyleEditor.cpp
@@ -40,22 +40,22 @@
 #include "nscore.h"
 
 class nsISupports;
 
 namespace mozilla {
 
 using namespace dom;
 
-static bool
-IsEmptyTextNode(HTMLEditor* aThis, nsINode* aNode)
+bool
+HTMLEditor::IsEmptyTextNode(nsINode& aNode)
 {
   bool isEmptyTextNode = false;
-  return EditorBase::IsTextNode(aNode) &&
-         NS_SUCCEEDED(aThis->IsEmptyNode(aNode, &isEmptyTextNode)) &&
+  return EditorBase::IsTextNode(&aNode) &&
+         NS_SUCCEEDED(IsEmptyNode(&aNode, &isEmptyTextNode)) &&
          isEmptyTextNode;
 }
 
 NS_IMETHODIMP
 HTMLEditor::SetInlineProperty(const nsAString& aProperty,
                               const nsAString& aAttribute,
                               const nsAString& aValue)
 {
@@ -340,17 +340,17 @@ HTMLEditor::SetInlinePropertyOnNodeImpl(
   if (!TagCanContain(*nsGkAtoms::span, aNode)) {
     if (aNode.HasChildren()) {
       nsTArray<OwningNonNull<nsIContent>> arrayOfNodes;
 
       // Populate the list.
       for (nsCOMPtr<nsIContent> child = aNode.GetFirstChild();
            child;
            child = child->GetNextSibling()) {
-        if (IsEditable(child) && !IsEmptyTextNode(this, child)) {
+        if (IsEditable(child) && !IsEmptyTextNode(*child)) {
           arrayOfNodes.AppendElement(*child);
         }
       }
 
       // Then loop through the list, set the property on each node.
       for (auto& node : arrayOfNodes) {
         nsresult rv = SetInlinePropertyOnNode(node, aProperty, aAttribute,
                                               aValue);
@@ -1046,17 +1046,17 @@ HTMLEditor::GetInlinePropertyBase(nsAtom
       nsCOMPtr<nsIContent> content = iter->GetCurrentNode()->AsContent();
 
       if (content->IsHTMLElement(nsGkAtoms::body)) {
         break;
       }
 
       // just ignore any non-editable nodes
       if (content->GetAsText() && (!IsEditable(content) ||
-                                   IsEmptyTextNode(this, content))) {
+                                   IsEmptyTextNode(*content))) {
         continue;
       }
       if (content->GetAsText()) {
         if (!isCollapsed && first && firstNodeInRange) {
           firstNodeInRange = false;
           if (range->StartOffset() == content->Length()) {
             continue;
           }
--- a/editor/libeditor/TextEditRules.cpp
+++ b/editor/libeditor/TextEditRules.cpp
@@ -550,28 +550,33 @@ TextEditRules::CollapseSelectionToTraili
     return NS_ERROR_EDITOR_DESTROYED;
   }
   if (NS_WARN_IF(error.Failed())) {
     return error.StealNSResult();
   }
   return NS_OK;
 }
 
-static inline already_AddRefed<nsINode>
-GetTextNode(Selection* aSelection)
+already_AddRefed<nsINode>
+TextEditRules::GetTextNodeAroundSelectionStartContainer()
 {
-  EditorRawDOMPoint selectionStartPoint(EditorBase::GetStartPoint(aSelection));
+  MOZ_ASSERT(IsEditorDataAvailable());
+
+  EditorRawDOMPoint selectionStartPoint(
+                      EditorBase::GetStartPoint(&SelectionRef()));
   if (NS_WARN_IF(!selectionStartPoint.IsSet())) {
     return nullptr;
   }
   if (selectionStartPoint.IsInTextNode()) {
     nsCOMPtr<nsINode> node = selectionStartPoint.GetContainer();
     return node.forget();
   }
   // This should be the root node, walk the tree looking for text nodes.
+  // XXX NodeIterator sets mutation observer even for this temporary use.
+  //     It's too expensive if this is called from a hot path.
   nsCOMPtr<nsINode> node = selectionStartPoint.GetContainer();
   RefPtr<NodeIterator> iter =
     new NodeIterator(node, NodeFilterBinding::SHOW_TEXT, nullptr);
   while (!EditorBase::IsTextNode(node)) {
     node = iter->NextNode(IgnoreErrors());
     if (!node) {
       return nullptr;
     }
@@ -1715,17 +1720,17 @@ TextEditRules::HideLastPWInput()
   nsAutoString hiddenText;
   FillBufWithPWChars(&hiddenText, mLastLength);
 
   uint32_t start, end;
   nsContentUtils::GetSelectionInTextControl(&SelectionRef(),
                                             TextEditorRef().GetRoot(),
                                             start, end);
 
-  nsCOMPtr<nsINode> selNode = GetTextNode(&SelectionRef());
+  nsCOMPtr<nsINode> selNode = GetTextNodeAroundSelectionStartContainer();
   if (NS_WARN_IF(!selNode)) {
     return NS_OK;
   }
 
   selNode->GetAsText()->ReplaceData(mLastStart, mLastLength, hiddenText,
                                     IgnoreErrors());
   if (NS_WARN_IF(!CanHandleEditAction())) {
     return NS_ERROR_EDITOR_DESTROYED;
--- a/editor/libeditor/TextEditRules.h
+++ b/editor/libeditor/TextEditRules.h
@@ -467,16 +467,24 @@ protected:
     MOZ_ASSERT(mTextEditor->IsInitialized());
     return true;
   }
 
 #ifdef DEBUG
   bool IsEditorDataAvailable() const { return !!mData; }
 #endif // #ifdef DEBUG
 
+  /**
+   * GetTextNodeAroundSelectionStartContainer() may return a Text node around
+   * start container of Selection.  If current selection container is not
+   * a text node, this will look for descendants and next siblings of the
+   * container.
+   */
+  inline already_AddRefed<nsINode> GetTextNodeAroundSelectionStartContainer();
+
   // A buffer we use to store the real value of password editors.
   nsString mPasswordText;
   // A buffer we use to track the IME composition string.
   nsString mPasswordIMEText;
   uint32_t mPasswordIMEIndex;
   // Magic node acts as placeholder in empty doc.
   nsCOMPtr<nsIContent> mBogusNode;
   // Cached selected node.
--- a/editor/libeditor/TextEditor.h
+++ b/editor/libeditor/TextEditor.h
@@ -19,17 +19,16 @@ class nsIContent;
 class nsIDocumentEncoder;
 class nsIOutputStream;
 class nsISelectionController;
 class nsITransferable;
 
 namespace mozilla {
 
 class AutoEditInitRulesTrigger;
-class HTMLEditRules;
 enum class EditAction : int32_t;
 
 namespace dom {
 class DragEvent;
 class Selection;
 } // namespace dom
 
 /**