Bug 1415062 - part 2: Editor should use Selection::Collapse(const RawRangeBoundary&) as far as possible r?m_kato draft
authorMasayuki Nakano <masayuki@d-toybox.com>
Tue, 07 Nov 2017 19:50:25 +0900
changeset 694737 a0220138ed2c2c097a8a4f63e6171cd0b9de0da4
parent 694736 e9c4656da418a8882c3d2bf6563049a68afb994e
child 694769 c873cb28d82a40376fcf450e63efbd7dc5090dfb
push id88219
push usermasayuki@d-toybox.com
push dateWed, 08 Nov 2017 06:07:35 +0000
reviewersm_kato
bugs1415062
milestone58.0a1
Bug 1415062 - part 2: Editor should use Selection::Collapse(const RawRangeBoundary&) as far as possible r?m_kato In some places, editor computes index from child node for collapsing selection at the child node. However, it's expensive. Therefore, editor should use Selection::Collapse(const RawRangeBoundary&) as far as possible. MozReview-Commit-ID: LF2MwASuXzZ
dom/base/RangeBoundary.h
editor/libeditor/CreateElementTransaction.cpp
editor/libeditor/EditorBase.cpp
editor/libeditor/EditorDOMPoint.h
editor/libeditor/HTMLEditRules.cpp
editor/libeditor/HTMLEditor.cpp
editor/libeditor/HTMLEditorDataTransfer.cpp
editor/libeditor/HTMLTableEditor.cpp
editor/libeditor/TextEditRules.cpp
editor/libeditor/TextEditor.cpp
--- a/dom/base/RangeBoundary.h
+++ b/dom/base/RangeBoundary.h
@@ -211,19 +211,23 @@ public:
   void
   Set(nsINode* aContainer, int32_t aOffset)
   {
     mParent = aContainer;
     mRef = nullptr;
     mOffset = mozilla::Some(aOffset);
   }
   void
-  Set(const nsIContent* aChild)
+  Set(const nsINode* aChild)
   {
     MOZ_ASSERT(aChild);
+    if (!aChild->IsContent()) {
+      Clear();
+      return;
+    }
     mParent = aChild->GetParentNode();
     mRef = aChild->GetPreviousSibling();
     if (!mRef) {
       mOffset = mozilla::Some(0);
     } else {
       mOffset.reset();
     }
   }
@@ -241,91 +245,97 @@ public:
 
   /**
    * 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.
+   *
+   * @return            true if there is a next sibling to refer.
    */
-  void
+  bool
   AdvanceOffset()
   {
     if (NS_WARN_IF(!mParent)) {
-      return;
+      return false;
     }
     EnsureRef();
     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;
+          return false;
         }
         mOffset = mozilla::Some(mOffset.value() + 1);
-        return;
+        return true;
       }
       mRef = mParent->GetFirstChild();
       if (NS_WARN_IF(!mRef)) {
         // No children in the container.
         mOffset = mozilla::Some(0);
-      } else {
-        mOffset = mozilla::Some(1);
+        return false;
       }
-      return;
+      mOffset = mozilla::Some(1);
+      return true;
     }
 
     nsIContent* nextSibling = mRef->GetNextSibling();
     if (NS_WARN_IF(!nextSibling)) {
       // Already referring the end of the container.
-      return;
+      return false;
     }
     mRef = nextSibling;
     if (mOffset.isSome()) {
       mOffset = mozilla::Some(mOffset.value() + 1);
     }
+    return true;
   }
 
   /**
    * 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.
+   *
+   * @return            true if there is a previous sibling to refer.
    */
-  void
+  bool
   RewindOffset()
   {
     if (NS_WARN_IF(!mParent)) {
-      return;
+      return false;
     }
     EnsureRef();
     if (!mRef) {
       if (NS_WARN_IF(mParent->IsContainerNode())) {
         // Already referring the start of the container
         mOffset = mozilla::Some(0);
-        return;
+        return false;
       }
       // 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;
+        return false;
       }
       mOffset = mozilla::Some(mOffset.value() - 1);
-      return;
+      return true;
     }
 
     mRef = mRef->GetPreviousSibling();
     if (mOffset.isSome()) {
       mOffset = mozilla::Some(mOffset.value() - 1);
     }
+    return true;
   }
 
   void
   SetAfterRef(nsINode* aParent, nsIContent* aRef)
   {
     mParent = aParent;
     mRef = aRef;
     if (!mRef) {
--- a/editor/libeditor/CreateElementTransaction.cpp
+++ b/editor/libeditor/CreateElementTransaction.cpp
@@ -8,16 +8,17 @@
 #include <algorithm>
 #include <stdio.h>
 
 #include "mozilla/dom/Element.h"
 #include "mozilla/dom/Selection.h"
 
 #include "mozilla/Casting.h"
 #include "mozilla/EditorBase.h"
+#include "mozilla/EditorDOMPoint.h"
 
 #include "nsAlgorithm.h"
 #include "nsAString.h"
 #include "nsDebug.h"
 #include "nsError.h"
 #include "nsIContent.h"
 #include "nsIEditor.h"
 #include "nsINode.h"
@@ -98,17 +99,24 @@ CreateElementTransaction::DoTransaction(
   if (!mEditorBase->GetShouldTxnSetSelection()) {
     // Do nothing - DOM range gravity will adjust selection
     return NS_OK;
   }
 
   RefPtr<Selection> selection = mEditorBase->GetSelection();
   NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
 
-  rv = selection->Collapse(mParent, mParent->IndexOf(mNewNode) + 1);
+  EditorRawDOMPoint afterNewNode(mNewNode);
+  if (NS_WARN_IF(!afterNewNode.AdvanceOffset())) {
+    // If mutation observer or mutation event listener moved or removed the
+    // new node, we hit this case.  Should we use script blocker while we're
+    // in this method?
+    return NS_ERROR_FAILURE;
+  }
+  rv = selection->Collapse(afterNewNode);
   NS_ASSERTION(!rv.Failed(),
                "selection could not be collapsed after insert");
   return NS_OK;
 }
 
 NS_IMETHODIMP
 CreateElementTransaction::UndoTransaction()
 {
--- a/editor/libeditor/EditorBase.cpp
+++ b/editor/libeditor/EditorBase.cpp
@@ -4273,32 +4273,40 @@ EditorBase::DeleteSelectionAndPrepareToC
     NS_ASSERTION(node->GetParentNode(),
                  "It's impossible to insert into chardata with no parent -- "
                  "fix the caller");
     NS_ENSURE_STATE(node->GetParentNode());
 
     uint32_t offset = selection->AnchorOffset();
 
     if (!offset) {
-      nsresult rv = selection->Collapse(node->GetParentNode(),
-                                        node->GetParentNode()->IndexOf(node));
+      EditorRawDOMPoint atNode(node);
+      if (NS_WARN_IF(!atNode.IsSetAndValid())) {
+        return NS_ERROR_FAILURE;
+      }
+      nsresult rv = selection->Collapse(atNode);
       MOZ_ASSERT(NS_SUCCEEDED(rv));
       NS_ENSURE_SUCCESS(rv, rv);
     } else if (offset == node->Length()) {
-      nsresult rv =
-        selection->Collapse(node->GetParentNode(),
-                            node->GetParentNode()->IndexOf(node) + 1);
+      EditorRawDOMPoint afterNode(node);
+      if (NS_WARN_IF(!afterNode.AdvanceOffset())) {
+        return NS_ERROR_FAILURE;
+      }
+      nsresult rv = selection->Collapse(afterNode);
       MOZ_ASSERT(NS_SUCCEEDED(rv));
       NS_ENSURE_SUCCESS(rv, rv);
     } else {
       nsCOMPtr<nsIDOMNode> tmp;
       nsresult rv = SplitNode(node->AsDOMNode(), offset, getter_AddRefs(tmp));
       NS_ENSURE_SUCCESS(rv, rv);
-      rv = selection->Collapse(node->GetParentNode(),
-                               node->GetParentNode()->IndexOf(node));
+      EditorRawDOMPoint atNode(node);
+      if (NS_WARN_IF(!atNode.IsSetAndValid())) {
+        return NS_ERROR_FAILURE;
+      }
+      rv = selection->Collapse(atNode);
       MOZ_ASSERT(NS_SUCCEEDED(rv));
       NS_ENSURE_SUCCESS(rv, rv);
     }
   }
   return NS_OK;
 }
 
 void
--- a/editor/libeditor/EditorDOMPoint.h
+++ b/editor/libeditor/EditorDOMPoint.h
@@ -44,25 +44,27 @@ public:
                      int32_t aOffset)
     : RangeBoundaryBase<ParentType, RefType>()
   {
     nsCOMPtr<nsINode> container = do_QueryInterface(aDOMContainer);
     this->Set(container, aOffset);
   }
 
   /**
-   * Different from RangeBoundary, aReferenceChild should be a child node
+   * Different from RangeBoundary, aPointedNode 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))
+  explicit EditorDOMPointBase(nsINode* aPointedNode)
+    : RangeBoundaryBase<ParentType, RefType>(
+        aPointedNode && aPointedNode->IsContent() ?
+          aPointedNode->GetParentNode() : nullptr,
+        aPointedNode && aPointedNode->IsContent() ?
+          GetRef(aPointedNode->AsContent()) : nullptr)
   {
   }
 
   EditorDOMPointBase(nsINode* aConatiner,
                      nsIContent* aPointedNode,
                      int32_t aOffset)
     : RangeBoundaryBase<ParentType, RefType>(aConatiner,
                                              GetRef(aPointedNode),
--- a/editor/libeditor/HTMLEditRules.cpp
+++ b/editor/libeditor/HTMLEditRules.cpp
@@ -1260,19 +1260,18 @@ HTMLEditRules::WillInsert(Selection& aSe
   if (priorNode && TextEditUtils::IsMozBR(priorNode)) {
     nsCOMPtr<Element> block1 = htmlEditor->GetBlock(selNode);
     nsCOMPtr<Element> block2 = htmlEditor->GetBlockNodeParent(priorNode);
 
     if (block1 && block1 == block2) {
       // If we are here then the selection is right after a mozBR that is in
       // the same block as the selection.  We need to move the selection start
       // to be before the mozBR.
-      selNode = priorNode->GetParentNode();
-      selOffset = selNode->IndexOf(priorNode);
-      nsresult rv = aSelection.Collapse(selNode, selOffset);
+      EditorRawDOMPoint point(priorNode);
+      nsresult rv = aSelection.Collapse(point.AsRaw());
       NS_ENSURE_SUCCESS_VOID(rv);
     }
   }
 
   if (mDidDeleteSelection &&
       (mTheAction == EditAction::insertText ||
        mTheAction == EditAction::insertIMEText ||
        mTheAction == EditAction::deleteSelection)) {
@@ -1488,17 +1487,19 @@ HTMLEditRules::WillInsertText(EditAction
             rv = wsObj.InsertText(subStr, address_of(curNode),
                                   address_of(selChild), &curOffset, doc);
             NS_ENSURE_SUCCESS(rv, rv);
           }
         }
       }
     }
     aSelection->SetInterlinePosition(false);
-    if (curNode) aSelection->Collapse(curNode, curOffset);
+    if (curNode) {
+      aSelection->Collapse(curNode, curOffset);
+    }
     // manually update the doc changed range so that AfterEdit will clean up
     // the correct portion of the document.
     if (!mDocChangeRange) {
       mDocChangeRange = new nsRange(selNode);
     }
 
     if (curNode) {
       rv = mDocChangeRange->SetStartAndEnd(selNode, selOffset,
@@ -1797,25 +1798,26 @@ HTMLEditRules::StandardBreakImpl(nsINode
       NS_ENSURE_STATE(aOffset != -1);
       node = linkParent;
     }
     brNode = wsObj.InsertBreak(address_of(node), &aOffset, nsIEditor::eNone);
     NS_ENSURE_TRUE(brNode, NS_ERROR_FAILURE);
   }
   node = brNode->GetParentNode();
   NS_ENSURE_TRUE(node, NS_ERROR_NULL_POINTER);
-  int32_t offset = node->IndexOf(brNode);
   if (bAfterBlock && bBeforeBlock) {
     // We just placed a br between block boundaries.  This is the one case
     // where we want the selection to be before the br we just placed, as the
     // br will be on a new line, rather than at end of prior line.
     aSelection.SetInterlinePosition(true);
-    nsresult rv = aSelection.Collapse(node, offset);
+    EditorRawDOMPoint point(brNode);
+    nsresult rv = aSelection.Collapse(point.AsRaw());
     NS_ENSURE_SUCCESS(rv, rv);
   } else {
+    int32_t offset = node->IndexOf(brNode);
     WSRunObject wsObj(htmlEditor, node, offset + 1);
     nsCOMPtr<nsINode> secondBR;
     int32_t visOffset = 0;
     WSType wsType;
     wsObj.NextVisibleNode(node, offset + 1, address_of(secondBR),
                           &visOffset, &wsType);
     if (wsType == WSType::br) {
       // The next thing after the break we inserted is another break.  Move the
@@ -2304,17 +2306,17 @@ HTMLEditRules::WillDeleteSelection(Selec
       // Are they both text nodes?  If so, join them!
       if (startNode == stepbrother && startNode->GetAsText() &&
           sibling->GetAsText()) {
         EditorDOMPoint pt = JoinNodesSmart(*sibling, *startNode->AsContent());
         if (NS_WARN_IF(!pt.IsSet())) {
           return NS_ERROR_FAILURE;
         }
         // Fix up selection
-        rv = aSelection->Collapse(pt.Container(), pt.Offset());
+        rv = aSelection->Collapse(pt.AsRaw());
         NS_ENSURE_SUCCESS(rv, rv);
       }
       rv = InsertBRIfNeeded(aSelection);
       NS_ENSURE_SUCCESS(rv, rv);
       return NS_OK;
     }
 
     if (wsType == WSType::otherBlock) {
@@ -2375,17 +2377,17 @@ HTMLEditRules::WillDeleteSelection(Selec
 
       if (bDeletedBR) {
         // Put selection at edge of block and we are done.
         NS_ENSURE_STATE(leafNode);
         EditorDOMPoint newSel = GetGoodSelPointForNode(*leafNode, aAction);
         if (NS_WARN_IF(!newSel.IsSet())) {
           return NS_ERROR_FAILURE;
         }
-        aSelection->Collapse(newSel.Container(), newSel.Offset());
+        aSelection->Collapse(newSel.AsRaw());
         return NS_OK;
       }
 
       // Else we are joining content to block
 
       nsCOMPtr<nsINode> selPointNode = startNode;
       int32_t selPointOffset = startOffset;
       {
@@ -2567,17 +2569,17 @@ HTMLEditRules::WillDeleteSelection(Selec
           // Join blocks
           NS_ENSURE_STATE(mHTMLEditor);
           EditorDOMPoint pt =
             mHTMLEditor->JoinNodeDeep(*leftParent, *rightParent);
           if (NS_WARN_IF(!pt.IsSet())) {
             return NS_ERROR_FAILURE;
           }
           // Fix up selection
-          rv = aSelection->Collapse(pt.Container(), pt.Offset());
+          rv = aSelection->Collapse(pt.AsRaw());
           NS_ENSURE_SUCCESS(rv, rv);
           return NS_OK;
         }
 
         // Else blocks not same type, or not siblings.  Delete everything
         // except table elements.
         join = true;
 
@@ -2787,17 +2789,17 @@ HTMLEditRules::GetGoodSelPointForNode(ns
     return EditorDOMPoint(&aNode, isPreviousAction ? aNode.Length() : 0);
   }
 
   if (NS_WARN_IF(!mHTMLEditor) ||
       NS_WARN_IF(!aNode.IsContent())) {
     return EditorDOMPoint();
   }
 
-  EditorDOMPoint ret(aNode.GetParentNode(), aNode.AsContent());
+  EditorDOMPoint ret(&aNode);
   if ((!aNode.IsHTMLElement(nsGkAtoms::br) ||
        mHTMLEditor->IsVisibleBRElement(&aNode)) && isPreviousAction) {
     ret.AdvanceOffset();
   }
   return ret;
 }
 
 EditActionResult
@@ -4543,35 +4545,32 @@ HTMLEditRules::WillOutdent(Selection& aS
   // Make sure selection didn't stick to last piece of content in old bq (only
   // a problem for collapsed selections)
   if (rememberedLeftBQ || rememberedRightBQ) {
     if (aSelection.Collapsed()) {
       // Push selection past end of rememberedLeftBQ
       NS_ENSURE_TRUE(aSelection.GetRangeAt(0), NS_OK);
       nsCOMPtr<nsINode> startNode =
         aSelection.GetRangeAt(0)->GetStartContainer();
-      int32_t startOffset = aSelection.GetRangeAt(0)->StartOffset();
       if (rememberedLeftBQ &&
           (startNode == rememberedLeftBQ ||
            EditorUtils::IsDescendantOf(*startNode, *rememberedLeftBQ))) {
         // Selection is inside rememberedLeftBQ - push it past it.
-        startNode = rememberedLeftBQ->GetParentNode();
-        startOffset = startNode ? 1 + startNode->IndexOf(rememberedLeftBQ) : 0;
-        aSelection.Collapse(startNode, startOffset);
+        EditorRawDOMPoint afterRememberedLeftBQ(rememberedLeftBQ);
+        afterRememberedLeftBQ.AdvanceOffset();
+        aSelection.Collapse(afterRememberedLeftBQ);
       }
       // And pull selection before beginning of rememberedRightBQ
       startNode = aSelection.GetRangeAt(0)->GetStartContainer();
-      startOffset = aSelection.GetRangeAt(0)->StartOffset();
       if (rememberedRightBQ &&
           (startNode == rememberedRightBQ ||
            EditorUtils::IsDescendantOf(*startNode, *rememberedRightBQ))) {
         // Selection is inside rememberedRightBQ - push it before it.
-        startNode = rememberedRightBQ->GetParentNode();
-        startOffset = startNode ? startNode->IndexOf(rememberedRightBQ) : -1;
-        aSelection.Collapse(startNode, startOffset);
+        EditorRawDOMPoint atRememberedRightBQ(rememberedRightBQ);
+        aSelection.Collapse(atRememberedRightBQ);
       }
     }
     return NS_OK;
   }
   return NS_OK;
 }
 
 
@@ -5179,47 +5178,55 @@ HTMLEditRules::CheckForEmptyBlock(nsINod
           // Adjust selection to be right before it
           nsresult rv = aSelection->Collapse(listParent, listOffset);
           NS_ENSURE_SUCCESS(rv, rv);
         }
         // Else just let selection percolate up.  We'll adjust it in
         // AfterEdit()
       }
     } else {
-      int32_t offset = blockParent->IndexOf(emptyBlock);
-
       if (aAction == nsIEditor::eNext || aAction == nsIEditor::eNextWord ||
           aAction == nsIEditor::eToEndOfLine) {
         // Move to the start of the next node, if any
         nsINode* child = emptyBlock->GetNextSibling();
+        int32_t offset = blockParent->IndexOf(emptyBlock);
         nsCOMPtr<nsIContent> nextNode =
           htmlEditor->GetNextNode(blockParent, offset + 1, child, true);
         if (nextNode) {
           EditorDOMPoint pt = GetGoodSelPointForNode(*nextNode, aAction);
-          nsresult rv = aSelection->Collapse(pt.Container(), pt.Offset());
+          nsresult rv = aSelection->Collapse(pt.AsRaw());
           NS_ENSURE_SUCCESS(rv, rv);
         } else {
           // Adjust selection to be right after it.
-          nsresult rv = aSelection->Collapse(blockParent, offset + 1);
+          EditorRawDOMPoint afterEmptyBlock(emptyBlock);
+          if (NS_WARN_IF(!afterEmptyBlock.AdvanceOffset())) {
+            return NS_ERROR_FAILURE;
+          }
+          nsresult rv = aSelection->Collapse(afterEmptyBlock);
           NS_ENSURE_SUCCESS(rv, rv);
         }
       } else if (aAction == nsIEditor::ePrevious ||
                  aAction == nsIEditor::ePreviousWord ||
                  aAction == nsIEditor::eToBeginningOfLine) {
         // Move to the end of the previous node
+        int32_t offset = blockParent->IndexOf(emptyBlock);
         nsCOMPtr<nsIContent> priorNode = htmlEditor->GetPriorNode(blockParent,
                                                                   offset,
                                                                   emptyBlock,
                                                                   true);
         if (priorNode) {
           EditorDOMPoint pt = GetGoodSelPointForNode(*priorNode, aAction);
-          nsresult rv = aSelection->Collapse(pt.Container(), pt.Offset());
+          nsresult rv = aSelection->Collapse(pt.AsRaw());
           NS_ENSURE_SUCCESS(rv, rv);
         } else {
-          nsresult rv = aSelection->Collapse(blockParent, offset + 1);
+          EditorRawDOMPoint afterEmptyBlock(emptyBlock);
+          if (NS_WARN_IF(!afterEmptyBlock.AdvanceOffset())) {
+            return NS_ERROR_FAILURE;
+          }
+          nsresult rv = aSelection->Collapse(afterEmptyBlock);
           NS_ENSURE_SUCCESS(rv, rv);
         }
       } else if (aAction != nsIEditor::eNone) {
         MOZ_CRASH("CheckForEmptyBlock doesn't support this action yet");
       }
     }
     NS_ENSURE_STATE(htmlEditor);
     *aHandled = true;
@@ -6616,20 +6623,22 @@ HTMLEditRules::ReturnInHeader(Selection&
       // Append a <br> to it
       nsCOMPtr<Element> brNode = htmlEditor->CreateBR(pNode, 0);
       NS_ENSURE_STATE(brNode);
 
       // Set selection to before the break
       rv = aSelection.Collapse(pNode, 0);
       NS_ENSURE_SUCCESS(rv, rv);
     } else {
-      headerParent = sibling->GetParentNode();
-      offset = headerParent ? headerParent->IndexOf(sibling) : -1;
+      EditorRawDOMPoint afterSibling(sibling);
+      if (NS_WARN_IF(!afterSibling.AdvanceOffset())) {
+        return NS_ERROR_FAILURE;
+      }
       // Put selection after break
-      rv = aSelection.Collapse(headerParent, offset + 1);
+      rv = aSelection.Collapse(afterSibling);
       NS_ENSURE_SUCCESS(rv, rv);
     }
   } else {
     // Put selection at front of righthand heading
     rv = aSelection.Collapse(&aHeader, 0);
     NS_ENSURE_SUCCESS(rv, rv);
   }
   return NS_OK;
@@ -6816,25 +6825,23 @@ HTMLEditRules::SplitParagraph(nsIDOMNode
   NS_ENSURE_SUCCESS(rv, rv);
   rv = InsertBRIfNeeded(*rightPara);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // selection to beginning of right hand para;
   // look inside any containers that are up front.
   nsCOMPtr<nsINode> rightParaNode = do_QueryInterface(rightPara);
   NS_ENSURE_STATE(mHTMLEditor && rightParaNode);
-  nsCOMPtr<nsIDOMNode> child =
-    GetAsDOMNode(mHTMLEditor->GetLeftmostChild(rightParaNode, true));
+  nsIContent* child = mHTMLEditor->GetLeftmostChild(rightParaNode, true);
   if (EditorBase::IsTextNode(child) ||
       mHTMLEditor->IsContainer(child)) {
     aSelection->Collapse(child,0);
   } else {
-    int32_t offset;
-    nsCOMPtr<nsIDOMNode> parent = EditorBase::GetNodeLocation(child, &offset);
-    aSelection->Collapse(parent,offset);
+    EditorRawDOMPoint atChild(child);
+    aSelection->Collapse(atChild);
   }
   return NS_OK;
 }
 
 /**
  * ReturnInListItem: do the right thing for returns pressed in list items
  */
 nsresult
@@ -6950,34 +6957,38 @@ HTMLEditRules::ReturnInListItem(Selectio
         }
 
         nsCOMPtr<Element> brNode;
         rv = htmlEditor->CopyLastEditableChildStyles(prevItem,
                                                      &aListItem,
                                                      getter_AddRefs(brNode));
         NS_ENSURE_SUCCESS(rv, rv);
         if (brNode) {
-          nsCOMPtr<nsINode> brParent = brNode->GetParentNode();
-          int32_t offset = brParent ? brParent->IndexOf(brNode) : -1;
-          rv = aSelection.Collapse(brParent, offset);
+          EditorRawDOMPoint atBrNode(brNode);
+          if (NS_WARN_IF(!atBrNode.IsSetAndValid())) {
+            return NS_ERROR_FAILURE;
+          }
+          rv = aSelection.Collapse(atBrNode);
           NS_ENSURE_SUCCESS(rv, rv);
           return NS_OK;
         }
       } else {
         WSRunObject wsObj(htmlEditor, &aListItem, 0);
         nsCOMPtr<nsINode> visNode;
         int32_t visOffset = 0;
         WSType wsType;
         wsObj.NextVisibleNode(&aListItem, 0, address_of(visNode),
                               &visOffset, &wsType);
         if (wsType == WSType::special || wsType == WSType::br ||
             visNode->IsHTMLElement(nsGkAtoms::hr)) {
-          nsCOMPtr<nsINode> parent = visNode->GetParentNode();
-          int32_t offset = parent ? parent->IndexOf(visNode) : -1;
-          rv = aSelection.Collapse(parent, offset);
+          EditorRawDOMPoint atVisNode(visNode);
+          if (NS_WARN_IF(!atVisNode.IsSetAndValid())) {
+            return NS_ERROR_FAILURE;
+          }
+          rv = aSelection.Collapse(atVisNode);
           NS_ENSURE_SUCCESS(rv, rv);
           return NS_OK;
         } else {
           rv = aSelection.Collapse(visNode, visOffset);
           NS_ENSURE_SUCCESS(rv, rv);
           return NS_OK;
         }
       }
@@ -7672,46 +7683,52 @@ HTMLEditRules::PinSelectionToNewBlock(Se
     return rv;
   }
   bool nodeBefore, nodeAfter;
   rv = nsRange::CompareNodeToRange(mNewBlock, range, &nodeBefore, &nodeAfter);
   NS_ENSURE_SUCCESS(rv, rv);
 
   if (nodeBefore && nodeAfter) {
     return NS_OK;  // selection is inside block
-  } else if (nodeBefore) {
+  }
+
+  if (nodeBefore) {
     // selection is after block.  put at end of block.
     NS_ENSURE_STATE(mHTMLEditor);
     nsCOMPtr<nsINode> tmp = mHTMLEditor->GetLastEditableChild(*mNewBlock);
     if (!tmp) {
       tmp = mNewBlock;
     }
-    uint32_t endPoint;
+    EditorRawDOMPoint endPoint;
     if (EditorBase::IsTextNode(tmp) ||
         mHTMLEditor->IsContainer(tmp)) {
-      endPoint = tmp->Length();
+      endPoint.Set(tmp, tmp->Length());
     } else {
-      tmp = EditorBase::GetNodeLocation(tmp, (int32_t*)&endPoint);
-      endPoint++;  // want to be after this node
-    }
-    return aSelection->Collapse(tmp, (int32_t)endPoint);
+      endPoint.Set(tmp);
+      if (NS_WARN_IF(!endPoint.AdvanceOffset())) {
+        return NS_ERROR_FAILURE;
+      }
+    }
+    return aSelection->Collapse(endPoint);
+  }
+
+  // selection is before block.  put at start of block.
+  NS_ENSURE_STATE(mHTMLEditor);
+  nsCOMPtr<nsINode> tmp = mHTMLEditor->GetFirstEditableChild(*mNewBlock);
+  if (!tmp) {
+    tmp = mNewBlock;
+  }
+  EditorRawDOMPoint atStartOfBlock;
+  if (EditorBase::IsTextNode(tmp) ||
+      mHTMLEditor->IsContainer(tmp)) {
+    atStartOfBlock.Set(tmp);
   } else {
-    // selection is before block.  put at start of block.
-    NS_ENSURE_STATE(mHTMLEditor);
-    nsCOMPtr<nsINode> tmp = mHTMLEditor->GetFirstEditableChild(*mNewBlock);
-    if (!tmp) {
-      tmp = mNewBlock;
-    }
-    int32_t offset;
-    if (EditorBase::IsTextNode(tmp) ||
-        mHTMLEditor->IsContainer(tmp)) {
-      tmp = EditorBase::GetNodeLocation(tmp, &offset);
-    }
-    return aSelection->Collapse(tmp, 0);
-  }
+    atStartOfBlock.Set(tmp, 0);
+  }
+  return aSelection->Collapse(atStartOfBlock);
 }
 
 void
 HTMLEditRules::CheckInterlinePosition(Selection& aSelection)
 {
   // If the selection isn't collapsed, do nothing.
   if (!aSelection.Collapsed()) {
     return;
@@ -7895,17 +7912,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.Container(), pt.Offset());
+  rv = aSelection->Collapse(pt.AsRaw());
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
   return NS_OK;
 }
 
 
 nsresult
--- a/editor/libeditor/HTMLEditor.cpp
+++ b/editor/libeditor/HTMLEditor.cpp
@@ -1081,20 +1081,28 @@ HTMLEditor::InsertBR(nsCOMPtr<nsIDOMNode
   int32_t selOffset;
   nsresult rv =
     GetStartNodeAndOffset(selection, getter_AddRefs(selNode), &selOffset);
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = CreateBR(selNode, selOffset, outBRNode);
   NS_ENSURE_SUCCESS(rv, rv);
 
+  selection->SetInterlinePosition(true);
+
   // position selection after br
-  selNode = GetNodeLocation(*outBRNode, &selOffset);
-  selection->SetInterlinePosition(true);
-  return selection->Collapse(selNode, selOffset+1);
+  nsCOMPtr<nsINode> brNode = do_QueryInterface(*outBRNode);
+  if (NS_WARN_IF(!brNode)) {
+    return NS_ERROR_FAILURE;
+  }
+  EditorRawDOMPoint afterBrNode(brNode);
+  if (NS_WARN_IF(!afterBrNode.AdvanceOffset())) {
+    return NS_ERROR_FAILURE;
+  }
+  return selection->Collapse(afterBrNode);
 }
 
 void
 HTMLEditor::CollapseSelectionToDeepestNonTableFirstChild(Selection* aSelection,
                                                          nsINode* aNode)
 {
   MOZ_ASSERT(aNode);
 
@@ -1687,19 +1695,22 @@ HTMLEditor::SetCaretAfterElement(nsIDOME
   }
 
   RefPtr<Selection> selection = GetSelection();
   NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
   nsCOMPtr<nsIDOMNode>parent;
   nsresult rv = aElement->GetParentNode(getter_AddRefs(parent));
   NS_ENSURE_SUCCESS(rv, rv);
   NS_ENSURE_TRUE(parent, NS_ERROR_NULL_POINTER);
-  int32_t offsetInParent = GetChildOffset(aElement, parent);
   // Collapse selection to just after desired element,
-  return selection->Collapse(parent, offsetInParent + 1);
+  EditorRawDOMPoint afterElement(element);
+  if (NS_WARN_IF(!afterElement.AdvanceOffset())) {
+    return NS_ERROR_FAILURE;
+  }
+  return selection->Collapse(afterElement);
 }
 
 NS_IMETHODIMP
 HTMLEditor::SetParagraphFormat(const nsAString& aParagraphFormat)
 {
   nsAutoString tag; tag.Assign(aParagraphFormat);
   ToLowerCase(tag);
   if (tag.EqualsLiteral("dd") || tag.EqualsLiteral("dt")) {
--- a/editor/libeditor/HTMLEditorDataTransfer.cpp
+++ b/editor/libeditor/HTMLEditorDataTransfer.cpp
@@ -13,16 +13,17 @@
 #include "WSRunObject.h"
 #include "mozilla/dom/DataTransfer.h"
 #include "mozilla/dom/DocumentFragment.h"
 #include "mozilla/dom/DOMStringList.h"
 #include "mozilla/dom/Selection.h"
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/Base64.h"
 #include "mozilla/BasicEvents.h"
+#include "mozilla/EditorDOMPoint.h"
 #include "mozilla/EditorUtils.h"
 #include "mozilla/OwningNonNull.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/SelectionState.h"
 #include "nsAString.h"
 #include "nsCOMPtr.h"
 #include "nsCRTGlue.h" // for CRLF
 #include "nsComponentManagerUtils.h"
@@ -652,18 +653,20 @@ HTMLEditor::DoInsertHTMLWithContext(cons
         nsCOMPtr<nsIContent> linkContent = do_QueryInterface(link);
         NS_ENSURE_STATE(linkContent || !link);
         nsCOMPtr<nsIContent> selContent = do_QueryInterface(selNode);
         NS_ENSURE_STATE(selContent || !selNode);
         nsCOMPtr<nsIContent> leftLink;
         SplitNodeDeep(*linkContent, *selContent, selOffset,
                       EmptyContainers::no, getter_AddRefs(leftLink));
         if (leftLink) {
-          selNode = GetNodeLocation(GetAsDOMNode(leftLink), &selOffset);
-          selection->Collapse(selNode, selOffset+1);
+          EditorRawDOMPoint afterLeftLink(leftLink);
+          if (afterLeftLink.AdvanceOffset()) {
+            selection->Collapse(afterLeftLink);
+          }
         }
       }
     }
   }
 
   return rules->DidDoAction(selection, &ruleInfo, rv);
 }
 
@@ -1893,20 +1896,19 @@ HTMLEditor::InsertAsPlaintextQuotation(c
 
   if (aNodeInserted && NS_SUCCEEDED(rv)) {
     *aNodeInserted = GetAsDOMNode(newNode);
     NS_IF_ADDREF(*aNodeInserted);
   }
 
   // Set the selection to just after the inserted node:
   if (NS_SUCCEEDED(rv) && newNode) {
-    nsCOMPtr<nsINode> parent = newNode->GetParentNode();
-    int32_t offset = parent ? parent->IndexOf(newNode) : -1;
-    if (parent) {
-      selection->Collapse(parent, offset + 1);
+    EditorRawDOMPoint afterNewNode(newNode);
+    if (afterNewNode.AdvanceOffset()) {
+      selection->Collapse(afterNewNode);
     }
   }
   return rv;
 }
 
 NS_IMETHODIMP
 HTMLEditor::StripCites()
 {
@@ -1973,20 +1975,19 @@ HTMLEditor::InsertAsCitedQuotation(const
 
   if (aNodeInserted && NS_SUCCEEDED(rv)) {
     *aNodeInserted = GetAsDOMNode(newNode);
     NS_IF_ADDREF(*aNodeInserted);
   }
 
   // Set the selection to just after the inserted node:
   if (NS_SUCCEEDED(rv) && newNode) {
-    nsCOMPtr<nsINode> parent = newNode->GetParentNode();
-    int32_t offset = parent ? parent->IndexOf(newNode) : -1;
-    if (parent) {
-      selection->Collapse(parent, offset + 1);
+    EditorRawDOMPoint afterNewNode(newNode);
+    if (afterNewNode.AdvanceOffset()) {
+      selection->Collapse(afterNewNode);
     }
   }
   return rv;
 }
 
 
 void RemoveBodyAndHead(nsINode& aNode)
 {
--- a/editor/libeditor/HTMLTableEditor.cpp
+++ b/editor/libeditor/HTMLTableEditor.cpp
@@ -4,16 +4,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include <stdio.h>
 
 #include "mozilla/HTMLEditor.h"
 
 #include "HTMLEditUtils.h"
 #include "mozilla/Assertions.h"
+#include "mozilla/EditorDOMPoint.h"
 #include "mozilla/EditorUtils.h"
 #include "mozilla/FlushType.h"
 #include "mozilla/dom/Selection.h"
 #include "mozilla/dom/Element.h"
 #include "nsAString.h"
 #include "nsAlgorithm.h"
 #include "nsCOMPtr.h"
 #include "nsDebug.h"
@@ -3152,18 +3153,25 @@ HTMLEditor::SetSelectionAfterTableEdit(n
     }
   } while (!done);
 
   // We didn't find a cell
   // Set selection to just before the table
   nsCOMPtr<nsIDOMNode> tableParent;
   nsresult rv = aTable->GetParentNode(getter_AddRefs(tableParent));
   if (NS_SUCCEEDED(rv) && tableParent) {
-    int32_t tableOffset = GetChildOffset(aTable, tableParent);
-    selection->Collapse(tableParent, tableOffset);
+    nsCOMPtr<nsIContent> table = do_QueryInterface(aTable);
+    if (NS_WARN_IF(!table)) {
+      return;
+    }
+    EditorRawDOMPoint atTable(table);
+    if (NS_WARN_IF(!atTable.IsSetAndValid())) {
+      return;
+    }
+    selection->Collapse(atTable);
     return;
   }
   // Last resort: Set selection to start of doc
   // (it's very bad to not have a valid selection!)
   SetSelectionAtDocumentStart(selection);
 }
 
 NS_IMETHODIMP
--- a/editor/libeditor/TextEditRules.cpp
+++ b/editor/libeditor/TextEditRules.cpp
@@ -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/. */
 
 #include "mozilla/TextEditRules.h"
 
 #include "TextEditUtils.h"
 #include "mozilla/Assertions.h"
+#include "mozilla/EditorDOMPoint.h"
 #include "mozilla/EditorUtils.h"
 #include "mozilla/LookAndFeel.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/TextComposition.h"
 #include "mozilla/TextEditor.h"
 #include "mozilla/dom/Element.h"
 #include "mozilla/dom/NodeIterator.h"
 #include "mozilla/dom/Selection.h"
@@ -489,18 +490,21 @@ TextEditRules::CollapseSelectionToTraili
   }
   nsINode* parentNode = selNode->GetParentNode();
   if (parentNode != root) {
     return NS_OK;
   }
 
   nsINode* nextNode = selNode->GetNextSibling();
   if (nextNode && TextEditUtils::IsMozBR(nextNode)) {
-    int32_t offsetInParent = EditorBase::GetChildOffset(selNode, parentNode);
-    rv = aSelection->Collapse(parentNode, offsetInParent + 1);
+    EditorRawDOMPoint afterSelNode(selNode);
+    if (NS_WARN_IF(!afterSelNode.AdvanceOffset())) {
+      return NS_ERROR_FAILURE;
+    }
+    rv = aSelection->Collapse(afterSelNode);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
   }
   return NS_OK;
 }
 
 static inline already_AddRefed<nsINode>
@@ -773,16 +777,18 @@ TextEditRules::WillInsertText(EditAction
 
     if (curNode) {
       // Make the caret attach to the inserted text, unless this text ends with a LF,
       // in which case make the caret attach to the next line.
       bool endsWithLF =
         !outString->IsEmpty() && outString->Last() == nsCRT::LF;
       aSelection->SetInterlinePosition(endsWithLF);
 
+      MOZ_ASSERT(!selChild,
+        "After inserting text into a text node, selChild should be nullptr");
       aSelection->Collapse(curNode, curOffset);
     }
   }
   ASSERT_PASSWORD_LENGTHS_EQUAL()
   return NS_OK;
 }
 
 nsresult
--- a/editor/libeditor/TextEditor.cpp
+++ b/editor/libeditor/TextEditor.cpp
@@ -4,16 +4,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/TextEditor.h"
 
 #include "InternetCiter.h"
 #include "TextEditUtils.h"
 #include "gfxFontUtils.h"
 #include "mozilla/Assertions.h"
+#include "mozilla/EditorDOMPoint.h"
 #include "mozilla/EditorUtils.h" // AutoPlaceholderBatch, AutoRules
 #include "mozilla/HTMLEditor.h"
 #include "mozilla/mozalloc.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/TextEditRules.h"
 #include "mozilla/TextComposition.h"
 #include "mozilla/TextEvents.h"
 #include "mozilla/dom/Selection.h"
@@ -471,29 +472,34 @@ TextEditor::CreateBRImpl(nsCOMPtr<nsIDOM
     if (NS_WARN_IF(!brNode)) {
       return NS_ERROR_FAILURE;
     }
     (*aInOutOffset)++;
   }
 
   *outBRNode = GetAsDOMNode(brNode);
   if (*outBRNode && (aSelect != eNone)) {
-    int32_t offset;
-    nsCOMPtr<nsINode> parent = GetNodeLocation(brNode, &offset);
-
     RefPtr<Selection> selection = GetSelection();
     NS_ENSURE_STATE(selection);
     if (aSelect == eNext) {
+      selection->SetInterlinePosition(true);
       // position selection after br
-      selection->SetInterlinePosition(true);
-      selection->Collapse(parent, offset + 1);
+      EditorRawDOMPoint afterBrNode(brNode);
+      if (NS_WARN_IF(!afterBrNode.AdvanceOffset())) {
+        return NS_OK;
+      }
+      selection->Collapse(afterBrNode);
     } else if (aSelect == ePrevious) {
+      selection->SetInterlinePosition(true);
       // position selection before br
-      selection->SetInterlinePosition(true);
-      selection->Collapse(parent, offset);
+      EditorRawDOMPoint atBrNode(brNode);
+      if (NS_WARN_IF(!atBrNode.IsSetAndValid())) {
+        return NS_OK;
+      }
+      selection->Collapse(atBrNode);
     }
   }
   return NS_OK;
 }
 
 
 NS_IMETHODIMP
 TextEditor::CreateBR(nsIDOMNode* aNode,
@@ -724,17 +730,19 @@ TextEditor::InsertLineBreak()
     // insert a linefeed character
     rv = InsertTextImpl(NS_LITERAL_STRING("\n"), address_of(selNode),
                         address_of(selChild), &selOffset, doc);
     if (!selNode) {
       rv = NS_ERROR_NULL_POINTER; // don't return here, so DidDoAction is called
     }
     if (NS_SUCCEEDED(rv)) {
       // set the selection to the correct location
-      rv = selection->Collapse(selNode, selOffset);
+      MOZ_ASSERT(!selChild,
+        "After inserting text into a text node, selChild should be nullptr");
+      rv = selection->Collapse(EditorRawDOMPoint(selNode, selOffset));
       if (NS_SUCCEEDED(rv)) {
         // see if we're at the end of the editor range
         nsCOMPtr<nsIDOMNode> endNode;
         int32_t endOffset;
         rv = GetEndNodeAndOffset(selection,
                                  getter_AddRefs(endNode), &endOffset);
 
         if (NS_SUCCEEDED(rv) &&