Bug 1415062 - part 1: Selection should have Collapse(const RawRangeBoundary&) and Collapse(const RawRangeBoundary&, ErrorResult&) for avoiding computing offset of child node in container r?mats draft
authorMasayuki Nakano <masayuki@d-toybox.com>
Tue, 07 Nov 2017 15:29:15 +0900
changeset 694736 e9c4656da418a8882c3d2bf6563049a68afb994e
parent 694565 40df5dd35fdb7ce3652fe4448ac8961c075c928e
child 694737 a0220138ed2c2c097a8a4f63e6171cd0b9de0da4
push id88219
push usermasayuki@d-toybox.com
push dateWed, 08 Nov 2017 06:07:35 +0000
reviewersmats
bugs1415062
milestone58.0a1
Bug 1415062 - part 1: Selection should have Collapse(const RawRangeBoundary&) and Collapse(const RawRangeBoundary&, ErrorResult&) for avoiding computing offset of child node in container r?mats Selection should have Collapse() methods which take RawRangeBoundary instead of a set of container and offset in it. Then, if caller know only child node but doesn't know offset in the container, neither callers, Selections nor nsRange needs to compute offset. This makes them avoid calling expensive method, nsINode::IndexOf(). MozReview-Commit-ID: 79IRajLe1FE
dom/base/Selection.cpp
dom/base/Selection.h
dom/base/nsRange.h
--- a/dom/base/Selection.cpp
+++ b/dom/base/Selection.cpp
@@ -9,16 +9,17 @@
  */
 
 #include "mozilla/dom/Selection.h"
 
 #include "mozilla/Attributes.h"
 #include "mozilla/AutoRestore.h"
 #include "mozilla/EventStates.h"
 #include "mozilla/HTMLEditor.h"
+#include "mozilla/RangeBoundary.h"
 
 #include "nsCOMPtr.h"
 #include "nsString.h"
 #include "nsFrameSelection.h"
 #include "nsISelectionListener.h"
 #include "nsContentCID.h"
 #include "nsDeviceContext.h"
 #include "nsIContent.h"
@@ -2539,137 +2540,138 @@ Selection::RemoveRange(nsRange& aRange, 
 
 /*
  * Collapse sets the whole selection to be one point.
  */
 NS_IMETHODIMP
 Selection::Collapse(nsIDOMNode* aContainer, int32_t aOffset)
 {
   nsCOMPtr<nsINode> container = do_QueryInterface(aContainer);
-  return Collapse(container, aOffset);
+  return Collapse(RawRangeBoundary(container, aOffset));
 }
 
 NS_IMETHODIMP
 Selection::CollapseNative(nsINode* aContainer, int32_t aOffset)
 {
-  return Collapse(aContainer, aOffset);
+  return Collapse(RawRangeBoundary(aContainer, aOffset));
 }
 
 void
 Selection::CollapseJS(nsINode* aContainer, uint32_t aOffset, ErrorResult& aRv)
 {
   AutoRestore<bool> calledFromJSRestorer(mCalledByJS);
   mCalledByJS = true;
   if (!aContainer) {
     RemoveAllRanges(aRv);
     return;
   }
-  Collapse(*aContainer, aOffset, aRv);
-}
-
-nsresult
-Selection::Collapse(nsINode* aContainer, int32_t aOffset)
-{
-  if (!aContainer) {
-    return NS_ERROR_INVALID_ARG;
-  }
-
-  ErrorResult result;
-  Collapse(*aContainer, static_cast<uint32_t>(aOffset), result);
-  return result.StealNSResult();
+  Collapse(RawRangeBoundary(aContainer, aOffset), aRv);
 }
 
 void
-Selection::Collapse(nsINode& aContainer, uint32_t aOffset, ErrorResult& aRv)
+Selection::Collapse(const RawRangeBoundary& aPoint, ErrorResult& aRv)
 {
   if (!mFrameSelection) {
     aRv.Throw(NS_ERROR_NOT_INITIALIZED); // Can't do selection
     return;
   }
 
-  if (aContainer.NodeType() == nsIDOMNode::DOCUMENT_TYPE_NODE) {
+  if (!aPoint.IsSet()) {
+    aRv.Throw(NS_ERROR_INVALID_ARG);
+    return;
+  }
+
+  if (aPoint.Container()->NodeType() == nsIDOMNode::DOCUMENT_TYPE_NODE) {
     aRv.Throw(NS_ERROR_DOM_INVALID_NODE_TYPE_ERR);
     return;
   }
 
-  if (aOffset > aContainer.Length()) {
+  // RawRangeBoundary::IsSetAndValid() checks if the point actually refers
+  // a child of the container when IsSet() is true.  If its offset hasn't been
+  // computed yet, this just checks it with its mRef.  So, we can avoid
+  // computing offset here.
+  if (!aPoint.IsSetAndValid()) {
     aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
     return;
   }
 
-  if (!HasSameRoot(aContainer)) {
+  if (!HasSameRoot(*aPoint.Container())) {
     // Return with no error
     return;
   }
 
-  nsCOMPtr<nsINode> container = &aContainer;
-
   RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
   frameSelection->InvalidateDesiredPos();
-  if (!IsValidSelectionPoint(frameSelection, container)) {
+  if (!IsValidSelectionPoint(frameSelection, aPoint.Container())) {
     aRv.Throw(NS_ERROR_FAILURE);
     return;
   }
   nsresult result;
 
   RefPtr<nsPresContext> presContext = GetPresContext();
-  if (!presContext || presContext->Document() != container->OwnerDoc()) {
+  if (!presContext ||
+      presContext->Document() != aPoint.Container()->OwnerDoc()) {
     aRv.Throw(NS_ERROR_FAILURE);
     return;
   }
 
   // Cache current range is if there is because it may be reusable.
   RefPtr<nsRange> oldRange = !mRanges.IsEmpty() ? mRanges[0].mRange : nullptr;
 
   // Delete all of the current ranges
   Clear(presContext);
 
   // Turn off signal for table selection
   frameSelection->ClearTableCellSelection();
 
   // Hack to display the caret on the right line (bug 1237236).
   if (frameSelection->GetHint() != CARET_ASSOCIATE_AFTER &&
-      container->IsContent()) {
+      aPoint.Container()->IsContent()) {
     int32_t frameOffset;
     nsTextFrame* f =
-      do_QueryFrame(nsCaret::GetFrameAndOffset(this, container,
-                                               aOffset, &frameOffset));
+      do_QueryFrame(nsCaret::GetFrameAndOffset(this, aPoint.Container(),
+                                               aPoint.Offset(), &frameOffset));
     if (f && f->IsAtEndOfLine() && f->HasSignificantTerminalNewline()) {
-      if ((container->AsContent() == f->GetContent() &&
-           f->GetContentEnd() == int32_t(aOffset)) ||
-          (container == f->GetContent()->GetParentNode() &&
-           container->IndexOf(f->GetContent()) + 1 == int32_t(aOffset))) {
+      // RawRangeBounary::Offset() causes computing offset if it's not been
+      // done yet.  However, it's called only when the container is a text
+      // node.  In such case, offset has always been set since it cannot have
+      // any children.  So, this doesn't cause computing offset with expensive
+      // method, nsINode::IndexOf().
+      if ((aPoint.Container()->AsContent() == f->GetContent() &&
+           f->GetContentEnd() == static_cast<int32_t>(aPoint.Offset())) ||
+          (aPoint.Container() == f->GetContent()->GetParentNode() &&
+           f->GetContent() == aPoint.GetPreviousSiblingOfChildAtOffset())) {
         frameSelection->SetHint(CARET_ASSOCIATE_AFTER);
       }
     }
   }
 
   RefPtr<nsRange> range;
   // If the old range isn't referred by anybody other than this method,
   // we should reuse it for reducing the recreation cost.
   if (oldRange && oldRange->GetRefCount() == 1) {
     range = Move(oldRange);
   } else if (mCachedRange) {
     range = Move(mCachedRange);
   } else {
-    range = new nsRange(container);
-  }
-  result = range->CollapseTo(container, aOffset);
+    range = new nsRange(aPoint.Container());
+  }
+  result = range->CollapseTo(aPoint);
   if (NS_FAILED(result)) {
     aRv.Throw(result);
     return;
   }
 
 #ifdef DEBUG_SELECTION
-  nsCOMPtr<nsIContent> content = do_QueryInterface(container);
-  nsCOMPtr<nsIDocument> doc = do_QueryInterface(container);
+  nsCOMPtr<nsIContent> content = do_QueryInterface(aPoint.Container());
+  nsCOMPtr<nsIDocument> doc = do_QueryInterface(aPoint.Container());
   printf ("Sel. Collapse to %p %s %d\n", container.get(),
           content ? nsAtomCString(content->NodeInfo()->NameAtom()).get()
                   : (doc ? "DOCUMENT" : "???"),
-          aOffset);
+          aPoint.Offset());
 #endif
 
   int32_t rangeIndex = -1;
   result = AddItem(range, &rangeIndex);
   if (NS_FAILED(result)) {
     aRv.Throw(result);
     return;
   }
--- a/dom/base/Selection.h
+++ b/dom/base/Selection.h
@@ -5,16 +5,17 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_Selection_h__
 #define mozilla_Selection_h__
 
 #include "nsIWeakReference.h"
 
 #include "mozilla/AutoRestore.h"
+#include "mozilla/RangeBoundary.h"
 #include "mozilla/TextRange.h"
 #include "mozilla/UniquePtr.h"
 #include "nsISelection.h"
 #include "nsISelectionController.h"
 #include "nsISelectionListener.h"
 #include "nsISelectionPrivate.h"
 #include "nsRange.h"
 #include "nsThreadUtils.h"
@@ -127,17 +128,29 @@ public:
    * then aRange is first scanned for -moz-user-select:none nodes and split up
    * into multiple ranges to exclude those before adding the resulting ranges
    * to this Selection.
    */
   nsresult      AddItem(nsRange* aRange, int32_t* aOutIndex, bool aNoStartSelect = false);
   nsresult      RemoveItem(nsRange* aRange);
   nsresult      RemoveCollapsedRanges();
   nsresult      Clear(nsPresContext* aPresContext);
-  nsresult      Collapse(nsINode* aContainer, int32_t aOffset);
+  nsresult      Collapse(nsINode* aContainer, int32_t aOffset)
+  {
+    if (!aContainer) {
+      return NS_ERROR_INVALID_ARG;
+    }
+    return Collapse(RawRangeBoundary(aContainer, aOffset));
+  }
+  nsresult      Collapse(const RawRangeBoundary& aPoint)
+  {
+    ErrorResult result;
+    Collapse(aPoint, result);
+    return result.StealNSResult();
+  }
   nsresult      Extend(nsINode* aContainer, int32_t aOffset);
   nsRange*      GetRangeAt(int32_t aIndex) const;
 
   // Get the anchor-to-focus range if we don't care which end is
   // anchor and which end is focus.
   const nsRange* GetAnchorFocusRange() const {
     return mAnchorFocusRange;
   }
@@ -286,17 +299,21 @@ public:
 
   void SetColors(const nsAString& aForeColor, const nsAString& aBackColor,
                  const nsAString& aAltForeColor, const nsAString& aAltBackColor,
                  mozilla::ErrorResult& aRv);
 
   void ResetColors(mozilla::ErrorResult& aRv);
 
   // Non-JS callers should use the following methods.
-  void Collapse(nsINode& aContainer, uint32_t aOffset, ErrorResult& aRv);
+  void Collapse(nsINode& aContainer, uint32_t aOffset, ErrorResult& aRv)
+  {
+    Collapse(RawRangeBoundary(&aContainer, aOffset), aRv);
+  }
+  void Collapse(const RawRangeBoundary& aPoint, ErrorResult& aRv);
   void CollapseToStart(mozilla::ErrorResult& aRv);
   void CollapseToEnd(mozilla::ErrorResult& aRv);
   void Extend(nsINode& aContainer, uint32_t aOffset, ErrorResult& aRv);
   void AddRange(nsRange& aRange, mozilla::ErrorResult& aRv);
   void SelectAllChildren(nsINode& aNode, mozilla::ErrorResult& aRv);
   void SetBaseAndExtent(nsINode& aAnchorNode, uint32_t aAnchorOffset,
                         nsINode& aFocusNode, uint32_t aFocusOffset,
                         mozilla::ErrorResult& aRv);
--- a/dom/base/nsRange.h
+++ b/dom/base/nsRange.h
@@ -215,17 +215,21 @@ public:
 
   /**
    * CollapseTo() works similar to call both SetStart() and SetEnd() with
    * same node and offset.  This just calls SetStartAndParent() to set
    * collapsed range at aContainer and aOffset.
    */
   nsresult CollapseTo(nsINode* aContainer, uint32_t aOffset)
   {
-    return SetStartAndEnd(aContainer, aOffset, aContainer, aOffset);
+    return CollapseTo(RawRangeBoundary(aContainer, aOffset));
+  }
+  nsresult CollapseTo(const RawRangeBoundary& aPoint)
+  {
+    return SetStartAndEnd(aPoint, aPoint);
   }
 
   /**
    * Retrieves node and offset for setting start or end of a range to
    * before or after aNode.
    */
   static nsINode* GetContainerAndOffsetAfter(nsINode* aNode, uint32_t* aOffset)
   {