Bug 1408544 - part 1: Reimplement EditorDOMPoint as a subclass of RangeBoundary r?m_kato, catalinb draft
authorMasayuki Nakano <masayuki@d-toybox.com>
Wed, 01 Nov 2017 14:41:03 +0900
changeset 693916 115e81107ed8981f28858318127deec1bf426f29
parent 693830 c2fe4b3b1b930b3e7fdb84eae44cec165394f322
child 693917 06be91e5fe089aacba40236419e4667c4ac69a0f
push id87977
push usermasayuki@d-toybox.com
push dateTue, 07 Nov 2017 03:51:22 +0000
reviewersm_kato, catalinb
bugs1408544
milestone58.0a1
Bug 1408544 - part 1: Reimplement EditorDOMPoint as a subclass of RangeBoundary r?m_kato, catalinb A lot of methods in editor returns a child offset with an out param when it returns its container and offset in the container. This is ugly hack for performance of nsINode::IndexOf(). However, there are a lot of regression since the relation between offset and child node can be broken really easily. So, we should make EditorDOMPoint as a subclass of RangeBoundary and manage a set of container, reference child and its offset in it (e.g., SetNextSibling() added by this patch). Note that RangeBoundary's performance is not good for temporary use if we set a point with offset, it immediately retrieves mRef. The following patch will improve this performance. MozReview-Commit-ID: 7mcJ1P1OjVr
dom/base/RangeBoundary.h
editor/libeditor/EditorBase.cpp
editor/libeditor/EditorBase.h
editor/libeditor/EditorDOMPoint.h
editor/libeditor/EditorUtils.h
editor/libeditor/HTMLEditRules.cpp
editor/libeditor/HTMLEditRules.h
editor/libeditor/WSRunObject.cpp
editor/libeditor/WSRunObject.h
editor/libeditor/moz.build
--- a/dom/base/RangeBoundary.h
+++ b/dom/base/RangeBoundary.h
@@ -78,16 +78,33 @@ public:
       NS_WARNING_ASSERTION(mRef || aOffset == 0,
                            "Constructing RangeBoundary with invalid value");
     }
 
     NS_WARNING_ASSERTION(!mRef || mRef->GetParentNode() == mParent,
                          "Constructing RangeBoundary with invalid value");
   }
 
+protected:
+  RangeBoundaryBase(nsINode* aContainer, nsIContent* aRef, int32_t aOffset)
+    : mParent(aContainer)
+    , mRef(aRef)
+    , mOffset(mozilla::Some(aOffset))
+  {
+    MOZ_RELEASE_ASSERT(aContainer,
+      "This constructor shouldn't be used when pointing nowhere");
+    if (!mRef) {
+      MOZ_ASSERT(mOffset.value() == 0);
+      return;
+    }
+    MOZ_ASSERT(mOffset.value() > 0);
+    MOZ_ASSERT(mParent->GetChildAt(mOffset.value() - 1) == mRef);
+  }
+
+public:
   RangeBoundaryBase()
     : mParent(nullptr)
     , mRef(nullptr)
   {
   }
 
   // Needed for initializing RawRangeBoundary from an existing RangeBoundary.
   template<typename PT, typename RT>
@@ -119,16 +136,52 @@ public:
     if (!mRef) {
       MOZ_ASSERT(Offset() == 0, "invalid RangeBoundary");
       return mParent->GetFirstChild();
     }
     MOZ_ASSERT(mParent->GetChildAt(Offset()) == mRef->GetNextSibling());
     return mRef->GetNextSibling();
   }
 
+  /**
+   * GetNextSiblingOfChildOffset() returns next sibling of a child at offset.
+   * If this refers after the last child or the container cannot have children,
+   * this returns nullptr with warning.
+   */
+  nsIContent*
+  GetNextSiblingOfChildAtOffset() const
+  {
+    if (NS_WARN_IF(!mParent) || NS_WARN_IF(!mParent->IsContainerNode())) {
+      return nullptr;
+    }
+    if (NS_WARN_IF(!mRef->GetNextSibling())) {
+      // Already referring the end of the container.
+      return nullptr;
+    }
+    return mRef->GetNextSibling()->GetNextSibling();
+  }
+
+  /**
+   * GetPreviousSiblingOfChildAtOffset() returns previous sibling of a child
+   * at offset.  If this refers the first child or the container cannot have
+   * children, this returns nullptr with warning.
+   */
+  nsIContent*
+  GetPreviousSiblingOfChildAtOffset() const
+  {
+    if (NS_WARN_IF(!mParent) || NS_WARN_IF(!mParent->IsContainerNode())) {
+      return nullptr;
+    }
+    if (NS_WARN_IF(!mRef)) {
+      // Already referring the start of the container.
+      return nullptr;
+    }
+    return mRef;
+  }
+
   uint32_t
   Offset() const
   {
     if (mOffset.isSome()) {
       return mOffset.value();
     }
 
     if (!mParent) {
@@ -178,16 +231,98 @@ public:
     }
 
     mOffset = mozilla::Some(aOffset);
 
     NS_WARNING_ASSERTION(!mRef || mRef->GetParentNode() == mParent,
                          "Setting RangeBoundary to invalid value");
   }
 
+  /**
+   * AdvanceOffset() tries to reference next sibling of mRef if its container
+   * can have children or increments offset if the container is a text node or
+   * something.
+   * If the container can have children and there is no next sibling, this
+   * outputs warning and does nothing.  So, callers need to check if there is
+   * next sibling which you need to refer.
+   */
+  void
+  AdvanceOffset()
+  {
+    if (NS_WARN_IF(!mParent)) {
+      return;
+    }
+    if (!mRef) {
+      if (!mParent->IsContainerNode()) {
+        // In text node or something, just increment the offset.
+        MOZ_ASSERT(mOffset.isSome());
+        if (NS_WARN_IF(mOffset.value() == mParent->Length())) {
+          // Already referring the end of the node.
+          return;
+        }
+        mOffset = mozilla::Some(mOffset.value() + 1);
+        return;
+      }
+      mRef = mParent->GetFirstChild();
+      if (NS_WARN_IF(!mRef)) {
+        // No children in the container.
+        mOffset = mozilla::Some(0);
+      } else {
+        mOffset = mozilla::Some(1);
+      }
+      return;
+    }
+
+    nsIContent* nextSibling = mRef->GetNextSibling();
+    if (NS_WARN_IF(!nextSibling)) {
+      // Already referring the end of the container.
+      return;
+    }
+    mRef = nextSibling;
+    if (mOffset.isSome()) {
+      mOffset = mozilla::Some(mOffset.value() + 1);
+    }
+  }
+
+  /**
+   * RewindOffset() tries to reference next sibling of mRef if its container
+   * can have children or decrements offset if the container is a text node or
+   * something.
+   * If the container can have children and there is no next previous, this
+   * outputs warning and does nothing.  So, callers need to check if there is
+   * previous sibling which you need to refer.
+   */
+  void
+  RewindOffset()
+  {
+    if (NS_WARN_IF(!mParent)) {
+      return;
+    }
+    if (!mRef) {
+      if (NS_WARN_IF(mParent->IsContainerNode())) {
+        // Already referring the start of the container
+        mOffset = mozilla::Some(0);
+        return;
+      }
+      // In text node or something, just decrement the offset.
+      MOZ_ASSERT(mOffset.isSome());
+      if (NS_WARN_IF(mOffset.value() == 0)) {
+        // Already referring the start of the node.
+        return;
+      }
+      mOffset = mozilla::Some(mOffset.value() - 1);
+      return;
+    }
+
+    mRef = mRef->GetPreviousSibling();
+    if (mOffset.isSome()) {
+      mOffset = mozilla::Some(mOffset.value() - 1);
+    }
+  }
+
   void
   SetAfterRef(nsINode* aParent, nsIContent* aRef)
   {
     mParent = aParent;
     mRef = aRef;
     if (!mRef) {
       mOffset = mozilla::Some(0);
     } else {
--- a/editor/libeditor/EditorBase.cpp
+++ b/editor/libeditor/EditorBase.cpp
@@ -23,21 +23,23 @@
 #include "InsertTextTransaction.h"      // for InsertTextTransaction
 #include "JoinNodeTransaction.h"        // for JoinNodeTransaction
 #include "PlaceholderTransaction.h"     // for PlaceholderTransaction
 #include "SplitNodeTransaction.h"       // for SplitNodeTransaction
 #include "StyleSheetTransactions.h"     // for AddStyleSheetTransaction, etc.
 #include "TextEditUtils.h"              // for TextEditUtils
 #include "mozInlineSpellChecker.h"      // for mozInlineSpellChecker
 #include "mozilla/CheckedInt.h"         // for CheckedInt
+#include "mozilla/EditorDOMPoint.h"     // for EditorDOMPoint
 #include "mozilla/EditorUtils.h"        // for AutoRules, etc.
 #include "mozilla/EditTransactionBase.h" // for EditTransactionBase
 #include "mozilla/FlushType.h"          // for FlushType::Frames
 #include "mozilla/IMEStateManager.h"    // for IMEStateManager
 #include "mozilla/Preferences.h"        // for Preferences
+#include "mozilla/RangeBoundary.h"      // for RawRangeBoundary, RangeBoundary
 #include "mozilla/dom/Selection.h"      // for Selection, etc.
 #include "mozilla/Services.h"           // for GetObserverService
 #include "mozilla/TextComposition.h"    // for TextComposition
 #include "mozilla/TextEvents.h"
 #include "mozilla/dom/Element.h"        // for Element, nsINode::AsElement
 #include "mozilla/dom/HTMLBodyElement.h"
 #include "mozilla/dom/Text.h"
 #include "mozilla/dom/Event.h"
@@ -3759,32 +3761,27 @@ EditorBase::IsTextNode(nsIDOMNode* aNode
     return false;
   }
 
   uint16_t nodeType;
   aNode->GetNodeType(&nodeType);
   return (nodeType == nsIDOMNode::TEXT_NODE);
 }
 
-/**
- * GetNodeAtRangeOffsetPoint() returns the node at this position in a range,
- * assuming that aParentOrNode is the node itself if it's a text node, or
- * the node's parent otherwise.
- */
+// static
 nsIContent*
-EditorBase::GetNodeAtRangeOffsetPoint(nsINode* aParentOrNode,
-                                      int32_t aOffset)
-{
-  if (NS_WARN_IF(!aParentOrNode)) {
+EditorBase::GetNodeAtRangeOffsetPoint(const RawRangeBoundary& aPoint)
+{
+  if (NS_WARN_IF(!aPoint.IsSet())) {
     return nullptr;
   }
-  if (aParentOrNode->GetAsText()) {
-    return aParentOrNode->AsContent();
-  }
-  return aParentOrNode->GetChildAt(aOffset);
+  if (aPoint.Container()->GetAsText()) {
+    return aPoint.Container()->AsContent();
+  }
+  return aPoint.GetChildAtOffset();
 }
 
 /**
  * GetStartNodeAndOffset() returns whatever the start parent & offset is of
  * the first range in the selection.
  */
 nsresult
 EditorBase::GetStartNodeAndOffset(Selection* aSelection,
@@ -4041,60 +4038,67 @@ EditorBase::JoinNodeDeep(nsIContent& aLe
   // While the rightmost children and their descendants of the left node match
   // the leftmost children and their descendants of the right node, join them
   // up.
 
   nsCOMPtr<nsIContent> leftNodeToJoin = &aLeftNode;
   nsCOMPtr<nsIContent> rightNodeToJoin = &aRightNode;
   nsCOMPtr<nsINode> parentNode = aRightNode.GetParentNode();
 
-  EditorDOMPoint ret;
+  nsCOMPtr<nsINode> resultNode = nullptr;
+  int32_t resultOffset = -1;
 
   while (leftNodeToJoin && rightNodeToJoin && parentNode &&
          AreNodesSameType(leftNodeToJoin, rightNodeToJoin)) {
     uint32_t length = leftNodeToJoin->Length();
 
-    ret.node = rightNodeToJoin;
-    ret.offset = length;
+    resultNode = rightNodeToJoin;
+    resultOffset = length;
 
     // Do the join
     nsresult rv = JoinNodes(*leftNodeToJoin, *rightNodeToJoin);
-    NS_ENSURE_SUCCESS(rv, EditorDOMPoint());
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return EditorDOMPoint();
+    }
 
     if (parentNode->GetAsText()) {
       // We've joined all the way down to text nodes, we're done!
-      return ret;
+      return EditorDOMPoint(resultNode, resultOffset);
     }
 
     // Get new left and right nodes, and begin anew
     parentNode = rightNodeToJoin;
     rightNodeToJoin = parentNode->GetChildAt(length);
     if (rightNodeToJoin) {
       leftNodeToJoin = rightNodeToJoin->GetPreviousSibling();
     } else {
       leftNodeToJoin = nullptr;
     }
 
     // Skip over non-editable nodes
     while (leftNodeToJoin && !IsEditable(leftNodeToJoin)) {
       leftNodeToJoin = leftNodeToJoin->GetPreviousSibling();
     }
     if (!leftNodeToJoin) {
-      return ret;
+      return EditorDOMPoint(resultNode, resultOffset);
     }
 
     while (rightNodeToJoin && !IsEditable(rightNodeToJoin)) {
       rightNodeToJoin = rightNodeToJoin->GetNextSibling();
     }
     if (!rightNodeToJoin) {
-      return ret;
+      return EditorDOMPoint(resultNode, resultOffset);
     }
   }
 
-  return ret;
+  if (NS_WARN_IF(!resultNode)) {
+    return EditorDOMPoint();
+  }
+
+  return EditorDOMPoint(resultNode, resultOffset);
 }
 
 void
 EditorBase::BeginUpdateViewBatch()
 {
   NS_PRECONDITION(mUpdateCount >= 0, "bad state");
 
   if (!mUpdateCount) {
--- a/editor/libeditor/EditorBase.h
+++ b/editor/libeditor/EditorBase.h
@@ -2,19 +2,21 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_EditorBase_h
 #define mozilla_EditorBase_h
 
 #include "mozilla/Assertions.h"         // for MOZ_ASSERT, etc.
+#include "mozilla/EditorDOMPoint.h"     // for EditorDOMPoint
 #include "mozilla/Maybe.h"              // for Maybe
 #include "mozilla/OwningNonNull.h"      // for OwningNonNull
 #include "mozilla/PresShell.h"          // for PresShell
+#include "mozilla/RangeBoundary.h"      // for RawRangeBoundary, RangeBoundary
 #include "mozilla/SelectionState.h"     // for RangeUpdater, etc.
 #include "mozilla/StyleSheet.h"         // for StyleSheet
 #include "mozilla/WeakPtr.h"            // for WeakPtr
 #include "mozilla/dom/Selection.h"
 #include "mozilla/dom/Text.h"
 #include "nsCOMPtr.h"                   // for already_AddRefed, nsCOMPtr
 #include "nsCycleCollectionParticipant.h"
 #include "nsGkAtoms.h"
@@ -118,17 +120,16 @@ class HTMLEditor;
 class InsertNodeTransaction;
 class InsertTextTransaction;
 class JoinNodeTransaction;
 class PlaceholderTransaction;
 class RemoveStyleSheetTransaction;
 class SplitNodeTransaction;
 class TextComposition;
 class TextEditor;
-struct EditorDOMPoint;
 
 namespace dom {
 class DataTransfer;
 class Element;
 class EventTarget;
 class Text;
 } // namespace dom
 
@@ -881,18 +882,27 @@ public:
   virtual bool AreNodesSameType(nsIContent* aNode1, nsIContent* aNode2);
 
   static bool IsTextNode(nsIDOMNode* aNode);
   static bool IsTextNode(nsINode* aNode)
   {
     return aNode->NodeType() == nsIDOMNode::TEXT_NODE;
   }
 
-  static nsIContent* GetNodeAtRangeOffsetPoint(nsINode* aParentOrNode,
-                                               int32_t aOffset);
+  /**
+   * GetNodeAtRangeOffsetPoint() returns the node at this position in a range,
+   * assuming that the container is the node itself if it's a text node, or
+   * the node's parent otherwise.
+   */
+  static nsIContent* GetNodeAtRangeOffsetPoint(nsINode* aContainer,
+                                               int32_t aOffset)
+  {
+    return GetNodeAtRangeOffsetPoint(RawRangeBoundary(aContainer, aOffset));
+  }
+  static nsIContent* GetNodeAtRangeOffsetPoint(const RawRangeBoundary& aPoint);
 
   static nsresult GetStartNodeAndOffset(Selection* aSelection,
                                         nsIDOMNode** outStartNode,
                                         int32_t* outStartOffset);
   static nsresult GetStartNodeAndOffset(Selection* aSelection,
                                         nsINode** aStartContainer,
                                         int32_t* aStartOffset);
   static nsresult GetEndNodeAndOffset(Selection* aSelection,
copy from editor/libeditor/EditorUtils.h
copy to editor/libeditor/EditorDOMPoint.h
--- a/editor/libeditor/EditorUtils.h
+++ b/editor/libeditor/EditorDOMPoint.h
@@ -1,438 +1,94 @@
 /* -*- 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/. */
 
 
-#ifndef mozilla_EditorUtils_h
-#define mozilla_EditorUtils_h
+#ifndef mozilla_EditorDOMPoint_h
+#define mozilla_EditorDOMPoint_h
 
-#include "mozilla/dom/Selection.h"
-#include "mozilla/EditorBase.h"
-#include "mozilla/GuardObjects.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/RangeBoundary.h"
 #include "nsCOMPtr.h"
-#include "nsDebug.h"
-#include "nsIDOMNode.h"
-#include "nsIEditor.h"
-#include "nscore.h"
-
-class nsAtom;
-class nsIContentIterator;
-class nsIDOMDocument;
-class nsIDOMEvent;
-class nsISimpleEnumerator;
-class nsITransferable;
-class nsRange;
+#include "nsIContent.h"
+#include "nsINode.h"
 
 namespace mozilla {
-template <class T> class OwningNonNull;
+
+template<typename ParentType, typename RefType>
+class EditorDOMPointBase;
 
-/***************************************************************************
- * EditActionResult is useful to return multiple results of an editor
- * action handler without out params.
- * Note that when you return an anonymous instance from a method, you should
- * use EditActionIgnored(), EditActionHandled() or EditActionCanceled() for
- * easier to read.  In other words, EditActionResult should be used when
- * declaring return type of a method, being an argument or defined as a local
- * variable.
- */
-class MOZ_STACK_CLASS EditActionResult final
+typedef EditorDOMPointBase<nsCOMPtr<nsINode>,
+                           nsCOMPtr<nsIContent>> EditorDOMPoint;
+typedef EditorDOMPointBase<nsINode*, nsIContent*> EditorRawDOMPoint;
+
+template<typename ParentType, typename RefType>
+class MOZ_STACK_CLASS EditorDOMPointBase final
+                        : public RangeBoundaryBase<ParentType, RefType>
 {
 public:
-  bool Succeeded() const { return NS_SUCCEEDED(mRv); }
-  bool Failed() const { return NS_FAILED(mRv); }
-  nsresult Rv() const { return mRv; }
-  bool Canceled() const { return mCanceled; }
-  bool Handled() const { return mHandled; }
-
-  EditActionResult SetResult(nsresult aRv)
+  EditorDOMPointBase()
+    : RangeBoundaryBase<ParentType, RefType>()
   {
-    mRv = aRv;
-    return *this;
-  }
-  EditActionResult MarkAsCanceled()
-  {
-    mCanceled = true;
-    return *this;
-  }
-  EditActionResult MarkAsHandled()
-  {
-    mHandled = true;
-    return *this;
   }
 
-  explicit EditActionResult(nsresult aRv)
-    : mRv(aRv)
-    , mCanceled(false)
-    , mHandled(false)
+  EditorDOMPointBase(nsINode* aConatiner,
+                     int32_t aOffset)
+    : RangeBoundaryBase<ParentType, RefType>(aConatiner,
+                                             aOffset)
   {
   }
 
-  EditActionResult& operator|=(const EditActionResult& aOther)
+  EditorDOMPointBase(nsIDOMNode* aDOMContainer,
+                     int32_t aOffset)
+    : RangeBoundaryBase<ParentType, RefType>()
   {
-    mCanceled |= aOther.mCanceled;
-    mHandled |= aOther.mHandled;
-    // When both result are same, keep the result.
-    if (mRv == aOther.mRv) {
-      return *this;
-    }
-    // If one of the results is error, use NS_ERROR_FAILURE.
-    if (Failed() || aOther.Failed()) {
-      mRv = NS_ERROR_FAILURE;
-    } else {
-      // Otherwise, use generic success code, NS_OK.
-      mRv = NS_OK;
-    }
-    return *this;
+    nsCOMPtr<nsINode> container = do_QueryInterface(aDOMContainer);
+    this->Set(container, aOffset);
   }
 
-private:
-  nsresult mRv;
-  bool mCanceled;
-  bool mHandled;
-
-  EditActionResult(nsresult aRv, bool aCanceled, bool aHandled)
-    : mRv(aRv)
-    , mCanceled(aCanceled)
-    , mHandled(aHandled)
-  {
-  }
-
-  EditActionResult()
-    : mRv(NS_ERROR_NOT_INITIALIZED)
-    , mCanceled(false)
-    , mHandled(false)
+  /**
+   * Different from RangeBoundary, aReferenceChild should be a child node
+   * which you want to refer.  So, set non-nullptr if offset is
+   * 0 - Length() - 1.  Otherwise, set nullptr, i.e., if offset is same as
+   * Length().
+   */
+  EditorDOMPointBase(nsINode* aContainer,
+                     nsIContent* aPointedNode)
+    : RangeBoundaryBase<ParentType, RefType>(aContainer,
+                                             GetRef(aPointedNode))
   {
   }
 
-  friend EditActionResult EditActionIgnored(nsresult aRv);
-  friend EditActionResult EditActionHandled(nsresult aRv);
-  friend EditActionResult EditActionCanceled(nsresult aRv);
-};
-
-/***************************************************************************
- * When an edit action handler (or its helper) does nothing,
- * EditActionIgnored should be returned.
- */
-inline EditActionResult
-EditActionIgnored(nsresult aRv = NS_OK)
-{
-  return EditActionResult(aRv, false, false);
-}
-
-/***************************************************************************
- * When an edit action handler (or its helper) handled and not canceled,
- * EditActionHandled should be returned.
- */
-inline EditActionResult
-EditActionHandled(nsresult aRv = NS_OK)
-{
-  return EditActionResult(aRv, false, true);
-}
+  EditorDOMPointBase(nsINode* aConatiner,
+                     nsIContent* aPointedNode,
+                     int32_t aOffset)
+    : RangeBoundaryBase<ParentType, RefType>(aConatiner,
+                                             GetRef(aPointedNode),
+                                             aOffset)
+  {
+  }
 
-/***************************************************************************
- * When an edit action handler (or its helper) handled and canceled,
- * EditActionHandled should be returned.
- */
-inline EditActionResult
-EditActionCanceled(nsresult aRv = NS_OK)
-{
-  return EditActionResult(aRv, true, true);
-}
-
-/***************************************************************************
- * stack based helper class for batching a collection of transactions inside a
- * placeholder transaction.
- */
-class MOZ_RAII AutoPlaceholderBatch final
-{
-private:
-  RefPtr<EditorBase> mEditorBase;
-  MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
+  template<typename PT, typename RT>
+  explicit EditorDOMPointBase(const RangeBoundaryBase<PT, RT>& aOther)
+    : RangeBoundaryBase<ParentType, RefType>(aOther)
+  {
+  }
 
-public:
-  explicit AutoPlaceholderBatch(EditorBase* aEditorBase
-                                MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
-    : mEditorBase(aEditorBase)
+  explicit EditorDOMPointBase(const RawRangeBoundary& aRawRangeBoundary)
+    : RangeBoundaryBase<ParentType, RefType>(aRawRangeBoundary)
   {
-    MOZ_GUARD_OBJECT_NOTIFIER_INIT;
-    BeginPlaceholderTransaction(nullptr);
-  }
-  AutoPlaceholderBatch(EditorBase* aEditorBase,
-                       nsAtom* aTransactionName
-                       MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
-    : mEditorBase(aEditorBase)
-  {
-    MOZ_GUARD_OBJECT_NOTIFIER_INIT;
-    BeginPlaceholderTransaction(aTransactionName);
-  }
-  ~AutoPlaceholderBatch()
-  {
-    if (mEditorBase) {
-      mEditorBase->EndPlaceholderTransaction();
-    }
   }
 
 private:
-  void BeginPlaceholderTransaction(nsAtom* aTransactionName)
-  {
-    if (mEditorBase) {
-      mEditorBase->BeginPlaceholderTransaction(aTransactionName);
-    }
-  }
-};
-
-/***************************************************************************
- * stack based helper class for saving/restoring selection.  Note that this
- * assumes that the nodes involved are still around afterwards!
- */
-class MOZ_RAII AutoSelectionRestorer final
-{
-private:
-  // Ref-counted reference to the selection that we are supposed to restore.
-  RefPtr<dom::Selection> mSelection;
-  EditorBase* mEditorBase;  // Non-owning ref to EditorBase.
-  MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
-
-public:
-  /**
-   * Constructor responsible for remembering all state needed to restore
-   * aSelection.
-   */
-  AutoSelectionRestorer(dom::Selection* aSelection,
-                        EditorBase* aEditorBase
-                        MOZ_GUARD_OBJECT_NOTIFIER_PARAM);
-
-  /**
-   * Destructor restores mSelection to its former state
-   */
-  ~AutoSelectionRestorer();
-
-  /**
-   * Abort() cancels to restore the selection.
-   */
-  void Abort();
-};
-
-/***************************************************************************
- * stack based helper class for StartOperation()/EndOperation() sandwich
- */
-class MOZ_RAII AutoRules final
-{
-public:
-  AutoRules(EditorBase* aEditorBase, EditAction aAction,
-            nsIEditor::EDirection aDirection
-            MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
-    : mEditorBase(aEditorBase)
-    , mDoNothing(false)
-  {
-    MOZ_GUARD_OBJECT_NOTIFIER_INIT;
-    // mAction will already be set if this is nested call
-    if (mEditorBase && !mEditorBase->mAction) {
-      mEditorBase->StartOperation(aAction, aDirection);
-    } else {
-      mDoNothing = true; // nested calls will end up here
-    }
-  }
-
-  ~AutoRules()
-  {
-    if (mEditorBase && !mDoNothing) {
-      mEditorBase->EndOperation();
-    }
-  }
-
-protected:
-  EditorBase* mEditorBase;
-  bool mDoNothing;
-  MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
-};
-
-/***************************************************************************
- * stack based helper class for turning off active selection adjustment
- * by low level transactions
- */
-class MOZ_RAII AutoTransactionsConserveSelection final
-{
-public:
-  explicit AutoTransactionsConserveSelection(EditorBase* aEditorBase
-                                             MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
-    : mEditorBase(aEditorBase)
-    , mOldState(true)
-  {
-    MOZ_GUARD_OBJECT_NOTIFIER_INIT;
-    if (mEditorBase) {
-      mOldState = mEditorBase->GetShouldTxnSetSelection();
-      mEditorBase->SetShouldTxnSetSelection(false);
-    }
-  }
-
-  ~AutoTransactionsConserveSelection()
-  {
-    if (mEditorBase) {
-      mEditorBase->SetShouldTxnSetSelection(mOldState);
-    }
-  }
-
-protected:
-  EditorBase* mEditorBase;
-  bool mOldState;
-  MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
-};
-
-/***************************************************************************
- * stack based helper class for batching reflow and paint requests.
- */
-class MOZ_RAII AutoUpdateViewBatch final
-{
-public:
-  explicit AutoUpdateViewBatch(EditorBase* aEditorBase
-                               MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
-    : mEditorBase(aEditorBase)
-  {
-    MOZ_GUARD_OBJECT_NOTIFIER_INIT;
-    NS_ASSERTION(mEditorBase, "null mEditorBase pointer!");
-
-    if (mEditorBase) {
-      mEditorBase->BeginUpdateViewBatch();
-    }
-  }
-
-  ~AutoUpdateViewBatch()
+  static nsIContent* GetRef(nsIContent* aPointedNode)
   {
-    if (mEditorBase) {
-      mEditorBase->EndUpdateViewBatch();
-    }
-  }
-
-protected:
-  EditorBase* mEditorBase;
-  MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
-};
-
-class MOZ_STACK_CLASS AutoRangeArray final
-{
-public:
-  explicit AutoRangeArray(dom::Selection* aSelection)
-  {
-    if (!aSelection) {
-      return;
-    }
-    uint32_t rangeCount = aSelection->RangeCount();
-    for (uint32_t i = 0; i < rangeCount; i++) {
-      mRanges.AppendElement(*aSelection->GetRangeAt(i));
-    }
+    MOZ_ASSERT(aPointedNode);
+    return aPointedNode ? aPointedNode->GetPreviousSibling() : nullptr;
   }
-
-  AutoTArray<mozilla::OwningNonNull<nsRange>, 8> mRanges;
-};
-
-/******************************************************************************
- * some helper classes for iterating the dom tree
- *****************************************************************************/
-
-class BoolDomIterFunctor
-{
-public:
-  virtual bool operator()(nsINode* aNode) const = 0;
-};
-
-class MOZ_RAII DOMIterator
-{
-public:
-  explicit DOMIterator(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM);
-
-  explicit DOMIterator(nsINode& aNode MOZ_GUARD_OBJECT_NOTIFIER_PARAM);
-  virtual ~DOMIterator();
-
-  nsresult Init(nsRange& aRange);
-
-  void AppendList(
-         const BoolDomIterFunctor& functor,
-         nsTArray<mozilla::OwningNonNull<nsINode>>& arrayOfNodes) const;
-
-protected:
-  nsCOMPtr<nsIContentIterator> mIter;
-  MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
-};
-
-class MOZ_RAII DOMSubtreeIterator final : public DOMIterator
-{
-public:
-  explicit DOMSubtreeIterator(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM);
-  virtual ~DOMSubtreeIterator();
-
-  nsresult Init(nsRange& aRange);
-};
-
-class TrivialFunctor final : public BoolDomIterFunctor
-{
-public:
-  // Used to build list of all nodes iterator covers
-  virtual bool operator()(nsINode* aNode) const
-  {
-    return true;
-  }
-};
-
-/******************************************************************************
- * general dom point utility struct
- *****************************************************************************/
-struct MOZ_STACK_CLASS EditorDOMPoint final
-{
-  nsCOMPtr<nsINode> node;
-  int32_t offset;
-
-  EditorDOMPoint()
-    : node(nullptr)
-    , offset(-1)
-  {}
-  EditorDOMPoint(nsINode* aNode, int32_t aOffset)
-    : node(aNode)
-    , offset(aOffset)
-  {}
-  EditorDOMPoint(nsIDOMNode* aNode, int32_t aOffset)
-    : node(do_QueryInterface(aNode))
-    , offset(aOffset)
-  {}
-
-  void SetPoint(nsINode* aNode, int32_t aOffset)
-  {
-    node = aNode;
-    offset = aOffset;
-  }
-  void SetPoint(nsIDOMNode* aNode, int32_t aOffset)
-  {
-    node = do_QueryInterface(aNode);
-    offset = aOffset;
-  }
-};
-
-class EditorUtils final
-{
-public:
-  // Note that aChild isn't a normal XPCOM outparam and won't get AddRef'ed.
-  static bool IsDescendantOf(nsINode* aNode, nsINode* aParent,
-                             nsIContent** aChild);
-  static bool IsDescendantOf(nsINode* aNode, nsINode* aParent,
-                             int32_t* aOffset = nullptr);
-  static bool IsDescendantOf(nsIDOMNode* aNode, nsIDOMNode* aParent,
-                             int32_t* aOffset = nullptr);
-  static bool IsLeafNode(nsIDOMNode* aNode);
-};
-
-class EditorHookUtils final
-{
-public:
-  static bool DoInsertionHook(nsIDOMDocument* aDoc, nsIDOMEvent* aEvent,
-                              nsITransferable* aTrans);
-
-private:
-  static nsresult GetHookEnumeratorFromDocument(
-                    nsIDOMDocument*aDoc,
-                    nsISimpleEnumerator** aEnumerator);
 };
 
 } // namespace mozilla
 
-#endif // #ifndef mozilla_EditorUtils_h
+#endif // #ifndef mozilla_EditorDOMPoint_h
--- a/editor/libeditor/EditorUtils.h
+++ b/editor/libeditor/EditorUtils.h
@@ -370,49 +370,16 @@ class TrivialFunctor final : public Bool
 public:
   // Used to build list of all nodes iterator covers
   virtual bool operator()(nsINode* aNode) const
   {
     return true;
   }
 };
 
-/******************************************************************************
- * general dom point utility struct
- *****************************************************************************/
-struct MOZ_STACK_CLASS EditorDOMPoint final
-{
-  nsCOMPtr<nsINode> node;
-  int32_t offset;
-
-  EditorDOMPoint()
-    : node(nullptr)
-    , offset(-1)
-  {}
-  EditorDOMPoint(nsINode* aNode, int32_t aOffset)
-    : node(aNode)
-    , offset(aOffset)
-  {}
-  EditorDOMPoint(nsIDOMNode* aNode, int32_t aOffset)
-    : node(do_QueryInterface(aNode))
-    , offset(aOffset)
-  {}
-
-  void SetPoint(nsINode* aNode, int32_t aOffset)
-  {
-    node = aNode;
-    offset = aOffset;
-  }
-  void SetPoint(nsIDOMNode* aNode, int32_t aOffset)
-  {
-    node = do_QueryInterface(aNode);
-    offset = aOffset;
-  }
-};
-
 class EditorUtils final
 {
 public:
   // Note that aChild isn't a normal XPCOM outparam and won't get AddRef'ed.
   static bool IsDescendantOf(nsINode* aNode, nsINode* aParent,
                              nsIContent** aChild);
   static bool IsDescendantOf(nsINode* aNode, nsINode* aParent,
                              int32_t* aOffset = nullptr);
--- a/editor/libeditor/HTMLEditRules.cpp
+++ b/editor/libeditor/HTMLEditRules.cpp
@@ -8,16 +8,17 @@
 
 #include <stdlib.h>
 
 #include "HTMLEditUtils.h"
 #include "TextEditUtils.h"
 #include "WSRunObject.h"
 #include "mozilla/Assertions.h"
 #include "mozilla/CSSEditUtils.h"
+#include "mozilla/EditorDOMPoint.h"
 #include "mozilla/EditorUtils.h"
 #include "mozilla/HTMLEditor.h"
 #include "mozilla/MathAlgorithms.h"
 #include "mozilla/Move.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/UniquePtr.h"
 #include "mozilla/Unused.h"
 #include "mozilla/dom/Selection.h"
@@ -2299,19 +2300,21 @@ HTMLEditRules::WillDeleteSelection(Selec
       if (sibling) {
         NS_ENSURE_STATE(mHTMLEditor);
         stepbrother = mHTMLEditor->GetNextHTMLSibling(sibling);
       }
       // Are they both text nodes?  If so, join them!
       if (startNode == stepbrother && startNode->GetAsText() &&
           sibling->GetAsText()) {
         EditorDOMPoint pt = JoinNodesSmart(*sibling, *startNode->AsContent());
-        NS_ENSURE_STATE(pt.node);
+        if (NS_WARN_IF(!pt.IsSet())) {
+          return NS_ERROR_FAILURE;
+        }
         // Fix up selection
-        rv = aSelection->Collapse(pt.node, pt.offset);
+        rv = aSelection->Collapse(pt.Container(), pt.Offset());
         NS_ENSURE_SUCCESS(rv, rv);
       }
       rv = InsertBRIfNeeded(aSelection);
       NS_ENSURE_SUCCESS(rv, rv);
       return NS_OK;
     }
 
     if (wsType == WSType::otherBlock) {
@@ -2369,18 +2372,20 @@ HTMLEditRules::WillDeleteSelection(Selec
           InDifferentTableElements(leftNode, rightNode)) {
         return NS_OK;
       }
 
       if (bDeletedBR) {
         // Put selection at edge of block and we are done.
         NS_ENSURE_STATE(leafNode);
         EditorDOMPoint newSel = GetGoodSelPointForNode(*leafNode, aAction);
-        NS_ENSURE_STATE(newSel.node);
-        aSelection->Collapse(newSel.node, newSel.offset);
+        if (NS_WARN_IF(!newSel.IsSet())) {
+          return NS_ERROR_FAILURE;
+        }
+        aSelection->Collapse(newSel.Container(), newSel.Offset());
         return NS_OK;
       }
 
       // Else we are joining content to block
 
       nsCOMPtr<nsINode> selPointNode = startNode;
       int32_t selPointOffset = startOffset;
       {
@@ -2558,19 +2563,21 @@ HTMLEditRules::WillDeleteSelection(Selec
           // First delete the selection
           NS_ENSURE_STATE(mHTMLEditor);
           rv = mHTMLEditor->DeleteSelectionImpl(aAction, aStripWrappers);
           NS_ENSURE_SUCCESS(rv, rv);
           // Join blocks
           NS_ENSURE_STATE(mHTMLEditor);
           EditorDOMPoint pt =
             mHTMLEditor->JoinNodeDeep(*leftParent, *rightParent);
-          NS_ENSURE_STATE(pt.node);
+          if (NS_WARN_IF(!pt.IsSet())) {
+            return NS_ERROR_FAILURE;
+          }
           // Fix up selection
-          rv = aSelection->Collapse(pt.node, pt.offset);
+          rv = aSelection->Collapse(pt.Container(), pt.Offset());
           NS_ENSURE_SUCCESS(rv, rv);
           return NS_OK;
         }
 
         // Else blocks not same type, or not siblings.  Delete everything
         // except table elements.
         join = true;
 
@@ -2767,29 +2774,33 @@ HTMLEditRules::GetGoodSelPointForNode(ns
              aAction == nsIEditor::ePreviousWord ||
              aAction == nsIEditor::eToBeginningOfLine ||
              aAction == nsIEditor::eToEndOfLine);
 
   bool isPreviousAction = (aAction == nsIEditor::ePrevious ||
                            aAction == nsIEditor::ePreviousWord ||
                            aAction == nsIEditor::eToBeginningOfLine);
 
-  NS_ENSURE_TRUE(mHTMLEditor, EditorDOMPoint());
+  if (NS_WARN_IF(!mHTMLEditor)) {
+    return EditorDOMPoint();
+  }
   if (aNode.GetAsText() || mHTMLEditor->IsContainer(&aNode) ||
       NS_WARN_IF(!aNode.GetParentNode())) {
     return EditorDOMPoint(&aNode, isPreviousAction ? aNode.Length() : 0);
   }
 
-  EditorDOMPoint ret;
-  ret.node = aNode.GetParentNode();
-  ret.offset = ret.node ? ret.node->IndexOf(&aNode) : -1;
-  NS_ENSURE_TRUE(mHTMLEditor, EditorDOMPoint());
+  if (NS_WARN_IF(!mHTMLEditor) ||
+      NS_WARN_IF(!aNode.IsContent())) {
+    return EditorDOMPoint();
+  }
+
+  EditorDOMPoint ret(aNode.GetParentNode(), aNode.AsContent());
   if ((!aNode.IsHTMLElement(nsGkAtoms::br) ||
        mHTMLEditor->IsVisibleBRElement(&aNode)) && isPreviousAction) {
-    ret.offset++;
+    ret.AdvanceOffset();
   }
   return ret;
 }
 
 EditActionResult
 HTMLEditRules::TryToJoinBlocks(nsIContent& aLeftNode,
                                nsIContent& aRightNode)
 {
@@ -3066,17 +3077,17 @@ HTMLEditRules::TryToJoinBlocks(nsIConten
   // Do br adjustment.
   nsCOMPtr<Element> brNode =
     CheckForInvisibleBR(*leftBlock, BRLocation::blockEnd);
   EditActionResult ret(NS_OK);
   if (mergeLists || leftBlock->NodeInfo()->NameAtom() ==
                     rightBlock->NodeInfo()->NameAtom()) {
     // Nodes are same type.  merge them.
     EditorDOMPoint pt = JoinNodesSmart(*leftBlock, *rightBlock);
-    if (pt.node && mergeLists) {
+    if (pt.IsSet() && mergeLists) {
       RefPtr<Element> newBlock =
         ConvertListType(rightBlock, existingList, nsGkAtoms::li);
     }
     ret.MarkAsHandled();
   } else {
     // Nodes are dissimilar types.
     ret |= MoveBlock(*leftBlock, *rightBlock, leftOffset, rightOffset);
     if (NS_WARN_IF(ret.Failed())) {
@@ -5159,34 +5170,34 @@ HTMLEditRules::CheckForEmptyBlock(nsINod
       if (aAction == nsIEditor::eNext || aAction == nsIEditor::eNextWord ||
           aAction == nsIEditor::eToEndOfLine) {
         // Move to the start of the next node, if any
         nsINode* child = emptyBlock->GetNextSibling();
         nsCOMPtr<nsIContent> nextNode =
           htmlEditor->GetNextNode(blockParent, offset + 1, child, true);
         if (nextNode) {
           EditorDOMPoint pt = GetGoodSelPointForNode(*nextNode, aAction);
-          nsresult rv = aSelection->Collapse(pt.node, pt.offset);
+          nsresult rv = aSelection->Collapse(pt.Container(), pt.Offset());
           NS_ENSURE_SUCCESS(rv, rv);
         } else {
           // Adjust selection to be right after it.
           nsresult rv = aSelection->Collapse(blockParent, offset + 1);
           NS_ENSURE_SUCCESS(rv, rv);
         }
       } else if (aAction == nsIEditor::ePrevious ||
                  aAction == nsIEditor::ePreviousWord ||
                  aAction == nsIEditor::eToBeginningOfLine) {
         // Move to the end of the previous node
         nsCOMPtr<nsIContent> priorNode = htmlEditor->GetPriorNode(blockParent,
                                                                   offset,
                                                                   emptyBlock,
                                                                   true);
         if (priorNode) {
           EditorDOMPoint pt = GetGoodSelPointForNode(*priorNode, aAction);
-          nsresult rv = aSelection->Collapse(pt.node, pt.offset);
+          nsresult rv = aSelection->Collapse(pt.Container(), pt.Offset());
           NS_ENSURE_SUCCESS(rv, rv);
         } else {
           nsresult rv = aSelection->Collapse(blockParent, offset + 1);
           NS_ENSURE_SUCCESS(rv, rv);
         }
       } else if (aAction != nsIEditor::eNone) {
         MOZ_CRASH("CheckForEmptyBlock doesn't support this action yet");
       }
@@ -5848,32 +5859,37 @@ HTMLEditRules::PromoteRange(nsRange& aRa
        return;
      }
   }
 
   // Make a new adjusted range to represent the appropriate block content.
   // This is tricky.  The basic idea is to push out the range endpoints to
   // truly enclose the blocks that we will affect.
 
-  EditorDOMPoint opStart =
+  // Make sure that the new range ends up to be in the editable section.
+  // XXX Looks like that this check wastes the time.  Perhaps, we should
+  //     implement a method which checks both two DOM points in the editor
+  //     root.
+  EditorDOMPoint startPoint =
     GetPromotedPoint(kStart, *startNode, startOffset, aOperationType);
-  EditorDOMPoint opEnd =
+  if (!htmlEditor->IsDescendantOfEditorRoot(
+         EditorBase::GetNodeAtRangeOffsetPoint(startPoint.AsRaw()))) {
+    return;
+  }
+  EditorDOMPoint endPoint =
     GetPromotedPoint(kEnd, *endNode, endOffset, aOperationType);
-
-  // Make sure that the new range ends up to be in the editable section.
+  EditorRawDOMPoint lastRawPoint(endPoint.AsRaw());
+  lastRawPoint.RewindOffset();
   if (!htmlEditor->IsDescendantOfEditorRoot(
-        EditorBase::GetNodeAtRangeOffsetPoint(opStart.node, opStart.offset)) ||
-      !htmlEditor->IsDescendantOfEditorRoot(
-        EditorBase::GetNodeAtRangeOffsetPoint(opEnd.node, opEnd.offset - 1))) {
+         EditorBase::GetNodeAtRangeOffsetPoint(lastRawPoint))) {
     return;
   }
 
   DebugOnly<nsresult> rv =
-    aRange.SetStartAndEnd(opStart.node, opStart.offset,
-                          opEnd.node, opEnd.offset);
+    aRange.SetStartAndEnd(startPoint.AsRaw(), endPoint.AsRaw());
   MOZ_ASSERT(NS_SUCCEEDED(rv));
 }
 
 class UniqueFunctor final : public BoolDomIterFunctor
 {
 public:
   explicit UniqueFunctor(nsTArray<OwningNonNull<nsINode>>& aArray)
     : mArray(aArray)
@@ -6395,39 +6411,44 @@ HTMLEditRules::GetHighestInlineParent(ns
 }
 
 /**
  * GetNodesFromPoint() constructs a list of nodes from a point that will be
  * operated on.
  */
 nsresult
 HTMLEditRules::GetNodesFromPoint(
-                 EditorDOMPoint aPoint,
+                 const EditorDOMPoint& aPoint,
                  EditAction aOperation,
                  nsTArray<OwningNonNull<nsINode>>& outArrayOfNodes,
                  TouchContent aTouchContent)
 {
-  NS_ENSURE_STATE(aPoint.node);
-  RefPtr<nsRange> range = new nsRange(aPoint.node);
-  nsresult rv = range->SetStart(aPoint.node, aPoint.offset);
+  if (NS_WARN_IF(!aPoint.IsSet())) {
+    return NS_ERROR_INVALID_ARG;
+  }
+  RefPtr<nsRange> range = new nsRange(aPoint.Container());
+  DebugOnly<nsresult> rv = range->SetStart(aPoint.Container(), aPoint.Offset());
   MOZ_ASSERT(NS_SUCCEEDED(rv));
 
   // Expand the range to include adjacent inlines
   PromoteRange(*range, aOperation);
 
   // Make array of ranges
   nsTArray<RefPtr<nsRange>> arrayOfRanges;
 
   // Stuff new opRange into array
   arrayOfRanges.AppendElement(range);
 
   // Use these ranges to contruct a list of nodes to act on
-  rv = GetNodesForOperation(arrayOfRanges, outArrayOfNodes, aOperation,
-                            aTouchContent);
-  NS_ENSURE_SUCCESS(rv, rv);
+  nsresult rv2 =
+    GetNodesForOperation(arrayOfRanges, outArrayOfNodes, aOperation,
+                         aTouchContent);
+  if (NS_WARN_IF(NS_FAILED(rv2))) {
+    return rv2;
+  }
 
   return NS_OK;
 }
 
 /**
  * GetNodesFromSelection() constructs a list of nodes from the selection that
  * will be operated on.
  */
@@ -7320,62 +7341,85 @@ HTMLEditRules::SplitAsNeeded(nsAtom& aTa
  * Returns the point where they're merged, or (nullptr, -1) on failure.
  */
 EditorDOMPoint
 HTMLEditRules::JoinNodesSmart(nsIContent& aNodeLeft,
                               nsIContent& aNodeRight)
 {
   // Caller responsible for left and right node being the same type
   nsCOMPtr<nsINode> parent = aNodeLeft.GetParentNode();
-  NS_ENSURE_TRUE(parent, EditorDOMPoint());
+  if (NS_WARN_IF(!parent)) {
+    return EditorDOMPoint();
+  }
   int32_t parOffset = parent->IndexOf(&aNodeLeft);
   nsCOMPtr<nsINode> rightParent = aNodeRight.GetParentNode();
 
   // If they don't have the same parent, first move the right node to after the
   // left one
   if (parent != rightParent) {
-    NS_ENSURE_TRUE(mHTMLEditor, EditorDOMPoint());
+    if (NS_WARN_IF(!mHTMLEditor)) {
+      return EditorDOMPoint();
+    }
     nsresult rv = mHTMLEditor->MoveNode(&aNodeRight, parent, parOffset);
-    NS_ENSURE_SUCCESS(rv, EditorDOMPoint());
-  }
-
-  EditorDOMPoint ret(&aNodeRight, aNodeLeft.Length());
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return EditorDOMPoint();
+    }
+  }
+
+  nsCOMPtr<nsINode> resultNode = &aNodeRight;
+  int32_t resultOffset = aNodeLeft.Length();
 
   // Separate join rules for differing blocks
   if (HTMLEditUtils::IsList(&aNodeLeft) || aNodeLeft.GetAsText()) {
     // For lists, merge shallow (wouldn't want to combine list items)
     nsresult rv = mHTMLEditor->JoinNodes(aNodeLeft, aNodeRight);
-    NS_ENSURE_SUCCESS(rv, EditorDOMPoint());
-    return ret;
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return EditorDOMPoint();
+    }
+    return EditorDOMPoint(resultNode, resultOffset);
   }
 
   // Remember the last left child, and first right child
-  NS_ENSURE_TRUE(mHTMLEditor, EditorDOMPoint());
+  if (NS_WARN_IF(!mHTMLEditor)) {
+    return EditorDOMPoint();
+  }
   nsCOMPtr<nsIContent> lastLeft = mHTMLEditor->GetLastEditableChild(aNodeLeft);
-  NS_ENSURE_TRUE(lastLeft, EditorDOMPoint());
-
-  NS_ENSURE_TRUE(mHTMLEditor, EditorDOMPoint());
+  if (NS_WARN_IF(!lastLeft)) {
+    return EditorDOMPoint();
+  }
+
+  if (NS_WARN_IF(!mHTMLEditor)) {
+    return EditorDOMPoint();
+  }
   nsCOMPtr<nsIContent> firstRight = mHTMLEditor->GetFirstEditableChild(aNodeRight);
-  NS_ENSURE_TRUE(firstRight, EditorDOMPoint());
+  if (NS_WARN_IF(!firstRight)) {
+    return EditorDOMPoint();
+  }
 
   // For list items, divs, etc., merge smart
-  NS_ENSURE_TRUE(mHTMLEditor, EditorDOMPoint());
+  if (NS_WARN_IF(!mHTMLEditor)) {
+    return EditorDOMPoint();
+  }
   nsresult rv = mHTMLEditor->JoinNodes(aNodeLeft, aNodeRight);
-  NS_ENSURE_SUCCESS(rv, EditorDOMPoint());
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return EditorDOMPoint();
+  }
 
   if (lastLeft && firstRight && mHTMLEditor &&
       mHTMLEditor->AreNodesSameType(lastLeft, firstRight) &&
       (lastLeft->GetAsText() || !mHTMLEditor ||
        (lastLeft->IsElement() && firstRight->IsElement() &&
         mHTMLEditor->mCSSEditUtils->ElementsSameStyle(lastLeft->AsElement(),
                                                   firstRight->AsElement())))) {
-    NS_ENSURE_TRUE(mHTMLEditor, EditorDOMPoint());
+    if (NS_WARN_IF(!mHTMLEditor)) {
+      return EditorDOMPoint();
+    }
     return JoinNodesSmart(*lastLeft, *firstRight);
   }
-  return ret;
+  return EditorDOMPoint(resultNode, resultOffset);
 }
 
 Element*
 HTMLEditRules::GetTopEnclosingMailCite(nsINode& aNode)
 {
   nsCOMPtr<Element> ret;
 
   for (nsCOMPtr<nsINode> node = &aNode; node; node = node->GetParentNode()) {
@@ -7833,17 +7877,17 @@ HTMLEditRules::AdjustSelection(Selection
   rv = FindNearSelectableNode(selNode, selOffset, child, aAction,
                               address_of(nearNode));
   NS_ENSURE_SUCCESS(rv, rv);
 
   if (!nearNode) {
     return NS_OK;
   }
   EditorDOMPoint pt = GetGoodSelPointForNode(*nearNode, aAction);
-  rv = aSelection->Collapse(pt.node, pt.offset);
+  rv = aSelection->Collapse(pt.Container(), pt.Offset());
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
   return NS_OK;
 }
 
 
 nsresult
--- a/editor/libeditor/HTMLEditRules.h
+++ b/editor/libeditor/HTMLEditRules.h
@@ -2,16 +2,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef HTMLEditRules_h
 #define HTMLEditRules_h
 
 #include "TypeInState.h"
+#include "mozilla/EditorDOMPoint.h" // for EditorDOMPoint
 #include "mozilla/SelectionState.h"
 #include "mozilla/TextEditRules.h"
 #include "nsCOMPtr.h"
 #include "nsIEditActionListener.h"
 #include "nsIEditor.h"
 #include "nsIHTMLEditor.h"
 #include "nsISupportsImpl.h"
 #include "nsTArray.h"
@@ -27,17 +28,16 @@ class nsINode;
 class nsRange;
 
 namespace mozilla {
 
 class EditActionResult;
 class HTMLEditor;
 class RulesInfo;
 class TextEditor;
-struct EditorDOMPoint;
 namespace dom {
 class Element;
 class Selection;
 } // namespace dom
 
 struct StyleCache final : public PropItem
 {
   bool mPresent;
@@ -184,18 +184,18 @@ protected:
    *
    * @param aNode           Reference to a block parent.
    * @param aInsertMozBR    true if this should insert a moz-<br> element.
    *                        Otherwise, i.e., this should insert a normal <br>
    *                        element, false.
    */
   nsresult InsertBRIfNeededInternal(nsINode& aNode, bool aInsertMozBR);
 
-  mozilla::EditorDOMPoint GetGoodSelPointForNode(nsINode& aNode,
-                                                 nsIEditor::EDirection aAction);
+  EditorDOMPoint GetGoodSelPointForNode(nsINode& aNode,
+                                        nsIEditor::EDirection aAction);
 
   /**
    * TryToJoinBlocks() tries to join two block elements.  The right element is
    * always joined to the left element.  If the elements are the same type and
    * not nested within each other, JoinNodesSmart() is called (example, joining
    * two list items together into one).  If the elements are not the same type,
    * or one is a descendant of the other, we instead destroy the right block
    * placing its children into leftblock.  DTD containment rules are followed
@@ -350,17 +350,17 @@ protected:
   nsresult GetNodesForOperation(
              nsTArray<RefPtr<nsRange>>& aArrayOfRanges,
              nsTArray<OwningNonNull<nsINode>>& aOutArrayOfNodes,
              EditAction aOperationType,
              TouchContent aTouchContent = TouchContent::yes);
   void GetChildNodesForOperation(
          nsINode& aNode,
          nsTArray<OwningNonNull<nsINode>>& outArrayOfNodes);
-  nsresult GetNodesFromPoint(EditorDOMPoint aPoint,
+  nsresult GetNodesFromPoint(const EditorDOMPoint& aPoint,
                              EditAction aOperation,
                              nsTArray<OwningNonNull<nsINode>>& outArrayOfNodes,
                              TouchContent aTouchContent);
   nsresult GetNodesFromSelection(
              Selection& aSelection,
              EditAction aOperation,
              nsTArray<OwningNonNull<nsINode>>& outArrayOfNodes,
              TouchContent aTouchContent = TouchContent::yes);
--- a/editor/libeditor/WSRunObject.cpp
+++ b/editor/libeditor/WSRunObject.cpp
@@ -4,16 +4,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "WSRunObject.h"
 
 #include "TextEditUtils.h"
 
 #include "mozilla/Assertions.h"
 #include "mozilla/Casting.h"
+#include "mozilla/EditorDOMPoint.h"
 #include "mozilla/EditorUtils.h"
 #include "mozilla/HTMLEditor.h"
 #include "mozilla/mozalloc.h"
 #include "mozilla/OwningNonNull.h"
 #include "mozilla/SelectionState.h"
 
 #include "nsAString.h"
 #include "nsCRT.h"
@@ -645,45 +646,44 @@ WSRunObject::GetWSNodes()
           mFirstNBSPNode = textNode;
           mFirstNBSPOffset = pos;
           // also keep track of latest nbsp so far
           if (!mLastNBSPNode) {
             mLastNBSPNode = textNode;
             mLastNBSPOffset = pos;
           }
         }
-        start.node = textNode;
-        start.offset = pos;
+        start.Set(textNode, pos);
       }
     }
   }
 
   while (!mStartNode) {
     // we haven't found the start of ws yet.  Keep looking
     nsCOMPtr<nsIContent> priorNode = GetPreviousWSNode(start, wsBoundingParent);
     if (priorNode) {
       if (IsBlockNode(priorNode)) {
-        mStartNode = start.node;
-        mStartOffset = start.offset;
+        mStartNode = start.Container();
+        mStartOffset = start.Offset();
         mStartReason = WSType::otherBlock;
         mStartReasonNode = priorNode;
       } else if (priorNode->IsNodeOfType(nsINode::eTEXT) &&
                  priorNode->IsEditable()) {
         RefPtr<Text> textNode = priorNode->GetAsText();
         mNodeArray.InsertElementAt(0, textNode);
         const nsTextFragment *textFrag;
         if (!textNode || !(textFrag = textNode->GetText())) {
           return NS_ERROR_NULL_POINTER;
         }
         uint32_t len = textNode->TextLength();
 
         if (len < 1) {
           // Zero length text node. Set start point to it
           // so we can get past it!
-          start.SetPoint(priorNode, 0);
+          start.Set(priorNode, 0);
         } else {
           for (int32_t pos = len - 1; pos >= 0; pos--) {
             // sanity bounds check the char position.  bug 136165
             if (uint32_t(pos) >= textFrag->GetLength()) {
               NS_NOTREACHED("looking beyond end of text fragment");
               continue;
             }
             char16_t theChar = textFrag->CharAt(pos);
@@ -699,35 +699,35 @@ WSRunObject::GetWSNodes()
               mFirstNBSPNode = textNode;
               mFirstNBSPOffset = pos;
               // also keep track of latest nbsp so far
               if (!mLastNBSPNode) {
                 mLastNBSPNode = textNode;
                 mLastNBSPOffset = pos;
               }
             }
-            start.SetPoint(textNode, pos);
+            start.Set(textNode, pos);
           }
         }
       } else {
         // it's a break or a special node, like <img>, that is not a block and not
         // a break but still serves as a terminator to ws runs.
-        mStartNode = start.node;
-        mStartOffset = start.offset;
+        mStartNode = start.Container();
+        mStartOffset = start.Offset();
         if (TextEditUtils::IsBreak(priorNode)) {
           mStartReason = WSType::br;
         } else {
           mStartReason = WSType::special;
         }
         mStartReasonNode = priorNode;
       }
     } else {
       // no prior node means we exhausted wsBoundingParent
-      mStartNode = start.node;
-      mStartOffset = start.offset;
+      mStartNode = start.Container();
+      mStartOffset = start.Offset();
       mStartReason = WSType::thisBlock;
       mStartReasonNode = wsBoundingParent;
     }
   }
 
   // then look ahead to find following ws nodes
   if (RefPtr<Text> textNode = mNode->GetAsText()) {
     // don't need to put it on list. it already is from code above
@@ -754,45 +754,45 @@ WSRunObject::GetWSNodes()
           mLastNBSPNode = textNode;
           mLastNBSPOffset = pos;
           // also keep track of earliest nbsp so far
           if (!mFirstNBSPNode) {
             mFirstNBSPNode = textNode;
             mFirstNBSPOffset = pos;
           }
         }
-        end.SetPoint(textNode, pos + 1);
+        end.Set(textNode, pos + 1);
       }
     }
   }
 
   while (!mEndNode) {
     // we haven't found the end of ws yet.  Keep looking
     nsCOMPtr<nsIContent> nextNode = GetNextWSNode(end, wsBoundingParent);
     if (nextNode) {
       if (IsBlockNode(nextNode)) {
         // we encountered a new block.  therefore no more ws.
-        mEndNode = end.node;
-        mEndOffset = end.offset;
+        mEndNode = end.Container();
+        mEndOffset = end.Offset();
         mEndReason = WSType::otherBlock;
         mEndReasonNode = nextNode;
       } else if (nextNode->IsNodeOfType(nsINode::eTEXT) &&
                  nextNode->IsEditable()) {
         RefPtr<Text> textNode = nextNode->GetAsText();
         mNodeArray.AppendElement(textNode);
         const nsTextFragment *textFrag;
         if (!textNode || !(textFrag = textNode->GetText())) {
           return NS_ERROR_NULL_POINTER;
         }
         uint32_t len = textNode->TextLength();
 
         if (len < 1) {
           // Zero length text node. Set end point to it
           // so we can get past it!
-          end.SetPoint(textNode, 0);
+          end.Set(textNode, 0);
         } else {
           for (uint32_t pos = 0; pos < len; pos++) {
             // sanity bounds check the char position.  bug 136165
             if (pos >= textFrag->GetLength()) {
               NS_NOTREACHED("looking beyond end of text fragment");
               continue;
             }
             char16_t theChar = textFrag->CharAt(pos);
@@ -808,36 +808,36 @@ WSRunObject::GetWSNodes()
               mLastNBSPNode = textNode;
               mLastNBSPOffset = pos;
               // also keep track of earliest nbsp so far
               if (!mFirstNBSPNode) {
                 mFirstNBSPNode = textNode;
                 mFirstNBSPOffset = pos;
               }
             }
-            end.SetPoint(textNode, pos + 1);
+            end.Set(textNode, pos + 1);
           }
         }
       } else {
         // we encountered a break or a special node, like <img>,
         // that is not a block and not a break but still
         // serves as a terminator to ws runs.
-        mEndNode = end.node;
-        mEndOffset = end.offset;
+        mEndNode = end.Container();
+        mEndOffset = end.Offset();
         if (TextEditUtils::IsBreak(nextNode)) {
           mEndReason = WSType::br;
         } else {
           mEndReason = WSType::special;
         }
         mEndReasonNode = nextNode;
       }
     } else {
       // no next node means we exhausted wsBoundingParent
-      mEndNode = end.node;
-      mEndOffset = end.offset;
+      mEndNode = end.Container();
+      mEndOffset = end.Offset();
       mEndReason = WSType::thisBlock;
       mEndReasonNode = wsBoundingParent;
     }
   }
 
   return NS_OK;
 }
 
@@ -1028,45 +1028,50 @@ WSRunObject::GetPreviousWSNodeInner(nsIN
       return child;
     }
   }
   // Else return the node itself
   return priorNode;
 }
 
 nsIContent*
-WSRunObject::GetPreviousWSNode(EditorDOMPoint aPoint,
+WSRunObject::GetPreviousWSNode(const EditorDOMPoint& aPoint,
                                nsINode* aBlockParent)
 {
   // Can't really recycle various getnext/prior routines because we
   // have special needs here.  Need to step into inline containers but
   // not block containers.
-  MOZ_ASSERT(aPoint.node && aBlockParent);
+  MOZ_ASSERT(aPoint.IsSet() && aBlockParent);
 
-  if (aPoint.node->NodeType() == nsIDOMNode::TEXT_NODE) {
-    return GetPreviousWSNodeInner(aPoint.node, aBlockParent);
+  if (aPoint.Container()->NodeType() == nsIDOMNode::TEXT_NODE) {
+    return GetPreviousWSNodeInner(aPoint.Container(), aBlockParent);
   }
-  if (!mHTMLEditor->IsContainer(aPoint.node)) {
-    return GetPreviousWSNodeInner(aPoint.node, aBlockParent);
+  if (!mHTMLEditor->IsContainer(aPoint.Container())) {
+    return GetPreviousWSNodeInner(aPoint.Container(), aBlockParent);
   }
 
-  if (!aPoint.offset) {
-    if (aPoint.node == aBlockParent) {
+  if (!aPoint.Offset()) {
+    if (aPoint.Container() == aBlockParent) {
       // We are at start of the block.
       return nullptr;
     }
 
     // We are at start of non-block container
-    return GetPreviousWSNodeInner(aPoint.node, aBlockParent);
+    return GetPreviousWSNodeInner(aPoint.Container(), aBlockParent);
   }
 
-  nsCOMPtr<nsIContent> startContent = do_QueryInterface(aPoint.node);
-  NS_ENSURE_TRUE(startContent, nullptr);
-  nsCOMPtr<nsIContent> priorNode = startContent->GetChildAt(aPoint.offset - 1);
-  NS_ENSURE_TRUE(priorNode, nullptr);
+  if (NS_WARN_IF(!aPoint.Container()->IsContent())) {
+    return nullptr;
+  }
+
+  nsCOMPtr<nsIContent> priorNode = aPoint.GetPreviousSiblingOfChildAtOffset();
+  if (NS_WARN_IF(!priorNode)) {
+    return nullptr;
+  }
+
   // We have a prior node.  If it's a block, return it.
   if (IsBlockNode(priorNode)) {
     return priorNode;
   }
   if (mHTMLEditor->IsContainer(priorNode)) {
     // Else if it's a container, get deep rightmost child
     nsCOMPtr<nsIContent> child = mHTMLEditor->GetRightmostChild(priorNode);
     if (child) {
@@ -1112,43 +1117,44 @@ WSRunObject::GetNextWSNodeInner(nsINode*
       return child;
     }
   }
   // Else return the node itself
   return nextNode;
 }
 
 nsIContent*
-WSRunObject::GetNextWSNode(EditorDOMPoint aPoint,
+WSRunObject::GetNextWSNode(const EditorDOMPoint& aPoint,
                            nsINode* aBlockParent)
 {
   // Can't really recycle various getnext/prior routines because we have
   // special needs here.  Need to step into inline containers but not block
   // containers.
-  MOZ_ASSERT(aPoint.node && aBlockParent);
+  MOZ_ASSERT(aPoint.IsSet() && aBlockParent);
 
-  if (aPoint.node->NodeType() == nsIDOMNode::TEXT_NODE) {
-    return GetNextWSNodeInner(aPoint.node, aBlockParent);
+  if (aPoint.Container()->NodeType() == nsIDOMNode::TEXT_NODE) {
+    return GetNextWSNodeInner(aPoint.Container(), aBlockParent);
   }
-  if (!mHTMLEditor->IsContainer(aPoint.node)) {
-    return GetNextWSNodeInner(aPoint.node, aBlockParent);
+  if (!mHTMLEditor->IsContainer(aPoint.Container())) {
+    return GetNextWSNodeInner(aPoint.Container(), aBlockParent);
   }
 
-  nsCOMPtr<nsIContent> startContent = do_QueryInterface(aPoint.node);
-  NS_ENSURE_TRUE(startContent, nullptr);
+  if (NS_WARN_IF(!aPoint.Container()->IsContent())) {
+    return nullptr;
+  }
 
-  nsCOMPtr<nsIContent> nextNode = startContent->GetChildAt(aPoint.offset);
+  nsCOMPtr<nsIContent> nextNode = aPoint.GetChildAtOffset();
   if (!nextNode) {
-    if (aPoint.node == aBlockParent) {
+    if (aPoint.Container() == aBlockParent) {
       // We are at end of the block.
       return nullptr;
     }
 
     // We are at end of non-block container
-    return GetNextWSNodeInner(aPoint.node, aBlockParent);
+    return GetNextWSNodeInner(aPoint.Container(), aBlockParent);
   }
 
   // We have a next node.  If it's a block, return it.
   if (IsBlockNode(nextNode)) {
     return nextNode;
   }
   if (mHTMLEditor->IsContainer(nextNode)) {
     // else if it's a container, get deep leftmost child
--- a/editor/libeditor/WSRunObject.h
+++ b/editor/libeditor/WSRunObject.h
@@ -7,24 +7,24 @@
 #define WSRunObject_h
 
 #include "nsCOMPtr.h"
 #include "nsIEditor.h" // for EDirection
 #include "nsINode.h"
 #include "nscore.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/dom/Text.h"
+#include "mozilla/EditorDOMPoint.h" // for EditorDOMPoint
 
 class nsIDOMNode;
 
 namespace mozilla {
 
 class HTMLEditor;
 class HTMLEditRules;
-struct EditorDOMPoint;
 
 // class WSRunObject represents the entire whitespace situation
 // around a given point.  It collects up a list of nodes that contain
 // whitespace and categorizes in up to 3 different WSFragments (detailed
 // below).  Each WSFragment is a collection of whitespace that is
 // either all insignificant, or that is significant.  A WSFragment could
 // consist of insignificant whitespace because it is after a block
 // boundary or after a break.  Or it could be insignificant because it
@@ -317,19 +317,21 @@ protected:
   nsINode* GetWSBoundingParent();
 
   nsresult GetWSNodes();
   void GetRuns();
   void ClearRuns();
   void MakeSingleWSRun(WSType aType);
   nsIContent* GetPreviousWSNodeInner(nsINode* aStartNode,
                                      nsINode* aBlockParent);
-  nsIContent* GetPreviousWSNode(EditorDOMPoint aPoint, nsINode* aBlockParent);
+  nsIContent* GetPreviousWSNode(const EditorDOMPoint& aPoint,
+                                nsINode* aBlockParent);
   nsIContent* GetNextWSNodeInner(nsINode* aStartNode, nsINode* aBlockParent);
-  nsIContent* GetNextWSNode(EditorDOMPoint aPoint, nsINode* aBlockParent);
+  nsIContent* GetNextWSNode(const EditorDOMPoint& aPoint,
+                            nsINode* aBlockParent);
   nsresult PrepareToDeleteRangePriv(WSRunObject* aEndObject);
   nsresult PrepareToSplitAcrossBlocksPriv();
   nsresult DeleteChars(nsINode* aStartNode, int32_t aStartOffset,
                        nsINode* aEndNode, int32_t aEndOffset);
   WSPoint GetCharAfter(nsINode* aNode, int32_t aOffset);
   WSPoint GetCharBefore(nsINode* aNode, int32_t aOffset);
   WSPoint GetCharAfter(const WSPoint& aPoint);
   WSPoint GetCharBefore(const WSPoint& aPoint);
--- a/editor/libeditor/moz.build
+++ b/editor/libeditor/moz.build
@@ -17,16 +17,17 @@ EXPORTS += [
     'nsIEditRules.h',
 ]
 
 EXPORTS.mozilla += [
     'ChangeStyleTransaction.h',
     'CSSEditUtils.h',
     'EditorBase.h',
     'EditorController.h',
+    'EditorDOMPoint.h',
     'EditorUtils.h',
     'EditTransactionBase.h',
     'HTMLEditor.h',
     'ManualNAC.h',
     'SelectionState.h',
     'TextEditor.h',
     'TextEditRules.h',
 ]