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=smaug
authorMasayuki Nakano <masayuki@d-toybox.com>
Tue, 07 Nov 2017 15:29:15 +0900
changeset 444141 e3aec6e0fb79a249c1344717d69eb16b9af5ad39
parent 444140 250063fc6ce206de9c58484163dd6981bb0c7fa1
child 444142 bda35e3d8ce4e6fbdc416b427f2200110ce52ff4
push id1618
push userCallek@gmail.com
push dateThu, 11 Jan 2018 17:45:48 +0000
treeherdermozilla-release@882ca853e05a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs1415062
milestone58.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1415062 - part 1: Selection should have Collapse(const RawRangeBoundary&) and Collapse(const RawRangeBoundary&, ErrorResult&) for avoiding computing offset of child node in container r=smaug 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)
   {