Commit 61af94ac authored by Masayuki Nakano's avatar Masayuki Nakano
Browse files

Bug 1367683 Optimize initializing nsRange r=smaug

nsRange::DoSetRange() adds/remove its root to/from mutation observer, initializes common ancestor, registers itself to the common ancestor, unregisters itself from old common ancestor, and notifies selection listeners of selection change.

However, those runtime cost is expensive but on the other hand, a lot of callers set both start and end of the range and that causes calling DoSetRange() twice.

This patch renames Set() to SetStartAndEnd() for easier to understand the meaning and make it call DoSetRange() only once.

MozReview-Commit-ID: FRV55tuBAgg

--HG--
extra : rebase_source : 67adf929cf119e2425f7d3741651217522094590
parent 242d05ee
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -6734,7 +6734,7 @@ already_AddRefed<nsRange>
nsIDocument::CreateRange(ErrorResult& rv)
{
  RefPtr<nsRange> range = new nsRange(this);
  nsresult res = range->Set(this, 0, this, 0);
  nsresult res = range->CollapseTo(this, 0);
  if (NS_FAILED(res)) {
    rv.Throw(res);
    return nullptr;
+93 −30
Original line number Diff line number Diff line
@@ -271,14 +271,17 @@ nsRange::CreateRange(nsINode* aStartParent, int32_t aStartOffset,
                     nsINode* aEndParent, int32_t aEndOffset,
                     nsRange** aRange)
{
  nsCOMPtr<nsIDOMNode> startDomNode = do_QueryInterface(aStartParent);
  nsCOMPtr<nsIDOMNode> endDomNode = do_QueryInterface(aEndParent);

  nsresult rv = CreateRange(startDomNode, aStartOffset, endDomNode, aEndOffset,
                            aRange);
  MOZ_ASSERT(aRange);
  *aRange = nullptr;

  RefPtr<nsRange> range = new nsRange(aStartParent);
  nsresult rv = range->SetStartAndEnd(aStartParent, aStartOffset,
                                      aEndParent, aEndOffset);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;

  }
  range.forget(aRange);
  return NS_OK;
}

/* static */
@@ -287,24 +290,9 @@ nsRange::CreateRange(nsIDOMNode* aStartParent, int32_t aStartOffset,
                     nsIDOMNode* aEndParent, int32_t aEndOffset,
                     nsRange** aRange)
{
  MOZ_ASSERT(aRange);
  *aRange = nullptr;

  nsCOMPtr<nsINode> startParent = do_QueryInterface(aStartParent);
  NS_ENSURE_ARG_POINTER(startParent);

  RefPtr<nsRange> range = new nsRange(startParent);

  // XXX this can be optimized by inlining SetStart/End and calling
  // DoSetRange *once*.
  nsresult rv = range->SetStart(startParent, aStartOffset);
  NS_ENSURE_SUCCESS(rv, rv);

  rv = range->SetEnd(aEndParent, aEndOffset);
  NS_ENSURE_SUCCESS(rv, rv);

  range.forget(aRange);
  return NS_OK;
  nsCOMPtr<nsINode> endParent = do_QueryInterface(aEndParent);
  return CreateRange(startParent, aStartOffset, endParent, aEndOffset, aRange);
}

/* static */
@@ -1149,6 +1137,15 @@ nsRange::GetCommonAncestorContainer(nsIDOMNode** aCommonParent)
  return rv.StealNSResult();
}

/* static */
bool
nsRange::IsValidOffset(nsINode* aNode, int32_t aOffset)
{
  return aNode &&
         aOffset >= 0 &&
         static_cast<size_t>(aOffset) <= aNode->Length();
}

nsINode*
nsRange::IsValidBoundary(nsINode* aNode)
{
@@ -1237,7 +1234,7 @@ nsRange::SetStart(nsINode* aParent, int32_t aOffset)
    return NS_ERROR_DOM_INVALID_NODE_TYPE_ERR;
  }

  if (aOffset < 0 || uint32_t(aOffset) > aParent->Length()) {
  if (!IsValidOffset(aParent, aOffset)) {
    return NS_ERROR_DOM_INDEX_SIZE_ERR;
  }

@@ -1274,7 +1271,9 @@ nsRange::SetStartBefore(nsINode& aNode, ErrorResult& aRv)
  }

  AutoInvalidateSelection atEndOfBlock(this);
  aRv = SetStart(aNode.GetParentNode(), IndexOf(&aNode));
  int32_t offset = -1;
  nsINode* parent = GetParentAndOffsetBefore(&aNode, &offset);
  aRv = SetStart(parent, offset);
}

NS_IMETHODIMP
@@ -1308,7 +1307,9 @@ nsRange::SetStartAfter(nsINode& aNode, ErrorResult& aRv)
  }

  AutoInvalidateSelection atEndOfBlock(this);
  aRv = SetStart(aNode.GetParentNode(), IndexOf(&aNode) + 1);
  int32_t offset = -1;
  nsINode* parent = GetParentAndOffsetAfter(&aNode, &offset);
  aRv = SetStart(parent, offset);
}

NS_IMETHODIMP
@@ -1365,7 +1366,7 @@ nsRange::SetEnd(nsINode* aParent, int32_t aOffset)
    return NS_ERROR_DOM_INVALID_NODE_TYPE_ERR;
  }

  if (aOffset < 0 || uint32_t(aOffset) > aParent->Length()) {
  if (!IsValidOffset(aParent, aOffset)) {
    return NS_ERROR_DOM_INDEX_SIZE_ERR;
  }

@@ -1384,6 +1385,64 @@ nsRange::SetEnd(nsINode* aParent, int32_t aOffset)
  return NS_OK;
}

nsresult
nsRange::SetStartAndEnd(nsINode* aStartParent, int32_t aStartOffset,
                        nsINode* aEndParent, int32_t aEndOffset)
{
  if (NS_WARN_IF(!aStartParent) || NS_WARN_IF(!aEndParent)) {
    return NS_ERROR_INVALID_ARG;
  }

  nsINode* newStartRoot = IsValidBoundary(aStartParent);
  if (!newStartRoot) {
    return NS_ERROR_DOM_INVALID_NODE_TYPE_ERR;
  }
  if (!IsValidOffset(aStartParent, aStartOffset)) {
    return NS_ERROR_DOM_INDEX_SIZE_ERR;
  }

  if (aStartParent == aEndParent) {
    if (!IsValidOffset(aEndParent, aEndOffset)) {
      return NS_ERROR_DOM_INDEX_SIZE_ERR;
    }
    // If the end offset is less than the start offset, this should be
    // collapsed at the end offset.
    if (aStartOffset > aEndOffset) {
      DoSetRange(aEndParent, aEndOffset, aEndParent, aEndOffset, newStartRoot);
    } else {
      DoSetRange(aStartParent, aStartOffset,
                 aEndParent, aEndOffset, newStartRoot);
    }
    return NS_OK;
  }

  nsINode* newEndRoot = IsValidBoundary(aEndParent);
  if (!newEndRoot) {
    return NS_ERROR_DOM_INVALID_NODE_TYPE_ERR;
  }
  if (!IsValidOffset(aEndParent, aEndOffset)) {
    return NS_ERROR_DOM_INDEX_SIZE_ERR;
  }

  // If they have different root, this should be collapsed at the end point.
  if (newStartRoot != newEndRoot) {
    DoSetRange(aEndParent, aEndOffset, aEndParent, aEndOffset, newEndRoot);
    return NS_OK;
  }

  // If the end point is before the start point, this should be collapsed at
  // the end point.
  if (nsContentUtils::ComparePoints(aStartParent, aStartOffset,
                                    aEndParent, aEndOffset) == 1) {
    DoSetRange(aEndParent, aEndOffset, aEndParent, aEndOffset, newEndRoot);
    return NS_OK;
  }

  // Otherwise, set the range as specified.
  DoSetRange(aStartParent, aStartOffset, aEndParent, aEndOffset, newStartRoot);
  return NS_OK;
}

void
nsRange::SetEndBeforeJS(nsINode& aNode, ErrorResult& aErr)
{
@@ -1402,7 +1461,9 @@ nsRange::SetEndBefore(nsINode& aNode, ErrorResult& aRv)
  }

  AutoInvalidateSelection atEndOfBlock(this);
  aRv = SetEnd(aNode.GetParentNode(), IndexOf(&aNode));
  int32_t offset = -1;
  nsINode* parent = GetParentAndOffsetBefore(&aNode, &offset);
  aRv = SetEnd(parent, offset);
}

NS_IMETHODIMP
@@ -1436,7 +1497,9 @@ nsRange::SetEndAfter(nsINode& aNode, ErrorResult& aRv)
  }

  AutoInvalidateSelection atEndOfBlock(this);
  aRv = SetEnd(aNode.GetParentNode(), IndexOf(&aNode) + 1);
  int32_t offset = -1;
  nsINode* parent = GetParentAndOffsetAfter(&aNode, &offset);
  aRv = SetEnd(parent, offset);
}

NS_IMETHODIMP
+50 −7
Original line number Diff line number Diff line
@@ -149,19 +149,61 @@ public:

  nsINode* GetCommonAncestor() const;
  void Reset();

  /**
   * SetStart() and SetEnd() sets start point or end point separately.
   * However, this is expensive especially when it's a range of Selection.
   * When you set both start and end of a range, you should use
   * SetStartAndEnd() instead.
   */
  nsresult SetStart(nsINode* aParent, int32_t aOffset);
  nsresult SetEnd(nsINode* aParent, int32_t aOffset);

  already_AddRefed<nsRange> CloneRange() const;

  nsresult Set(nsINode* aStartParent, int32_t aStartOffset,
               nsINode* aEndParent, int32_t aEndOffset)
  /**
   * SetStartAndEnd() works similar to call both SetStart() and SetEnd().
   * Different from calls them separately, this does nothing if either
   * the start point or the end point is invalid point.
   * If the specified start point is after the end point, the range will be
   * collapsed at the end point.  Similarly, if they are in different root,
   * the range will be collapsed at the end point.
   */
  nsresult SetStartAndEnd(nsINode* aStartParent, int32_t aStartOffset,
                          nsINode* aEndParent, int32_t aEndOffset);

  /**
   * CollapseTo() works similar to call both SetStart() and SetEnd() with
   * same node and offset.  This just calls SetStartAndParent() to set
   * collapsed range at aParent and aOffset.
   */
  nsresult CollapseTo(nsINode* aParent, int32_t aOffset)
  {
    // If this starts being hot, we may be able to optimize this a bit,
    // but for now just set start and end separately.
    nsresult rv = SetStart(aStartParent, aStartOffset);
    NS_ENSURE_SUCCESS(rv, rv);
    return SetStartAndEnd(aParent, aOffset, aParent, aOffset);
  }

    return SetEnd(aEndParent, aEndOffset);
  /**
   * Retrieves node and offset for setting start or end of a range to
   * before or after aNode.
   */
  static nsINode* GetParentAndOffsetAfter(nsINode* aNode, int32_t* aOffset)
  {
    MOZ_ASSERT(aNode);
    MOZ_ASSERT(aOffset);
    nsINode* parentNode = aNode->GetParentNode();
    *aOffset = parentNode ? parentNode->IndexOf(aNode) : -1;
    if (*aOffset >= 0) {
      (*aOffset)++;
    }
    return parentNode;
  }
  static nsINode* GetParentAndOffsetBefore(nsINode* aNode, int32_t* aOffset)
  {
    MOZ_ASSERT(aNode);
    MOZ_ASSERT(aOffset);
    nsINode* parentNode = aNode->GetParentNode();
    *aOffset = parentNode ? parentNode->IndexOf(aNode) : -1;
    return parentNode;
  }

  NS_IMETHOD GetUsedFontFaces(nsIDOMFontFaceList** aResult);
@@ -314,6 +356,7 @@ protected:
  void RegisterCommonAncestor(nsINode* aNode);
  void UnregisterCommonAncestor(nsINode* aNode);
  nsINode* IsValidBoundary(nsINode* aNode);
  static bool IsValidOffset(nsINode* aNode, int32_t aOffset);

  // CharacterDataChanged set aNotInsertedYet to true to disable an assertion
  // and suppress re-registering a range common ancestor node since
+2 −7
Original line number Diff line number Diff line
@@ -985,11 +985,7 @@ ContentEventHandler::SetRangeFromFlatTextOffset(nsRange* aRange,

  // Special case like <br contenteditable>
  if (!mRootContent->HasChildren()) {
    nsresult rv = aRange->SetStart(mRootContent, 0);
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return rv;
    }
    rv = aRange->SetEnd(mRootContent, 0);
    nsresult rv = aRange->CollapseTo(mRootContent, 0);
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return rv;
    }
@@ -2883,8 +2879,7 @@ ContentEventHandler::AdjustCollapsedRangeMaybeIntoTextNode(nsRange* aRange)
    return NS_OK;
  }

  nsresult rv = aRange->Set(childNode, offsetInChildNode,
                            childNode, offsetInChildNode);
  nsresult rv = aRange->CollapseTo(childNode, offsetInChildNode);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }
+61 −50
Original line number Diff line number Diff line
@@ -1445,16 +1445,15 @@ HTMLEditRules::WillInsertText(EditAction aAction,
    if (!mDocChangeRange) {
      mDocChangeRange = new nsRange(selNode);
    }
    rv = mDocChangeRange->SetStart(selNode, selOffset);
    NS_ENSURE_SUCCESS(rv, rv);

    if (curNode) {
      rv = mDocChangeRange->SetEnd(curNode, curOffset);
      rv = mDocChangeRange->SetStartAndEnd(selNode, selOffset,
                                           curNode, curOffset);
      if (NS_WARN_IF(NS_FAILED(rv))) {
        return rv;
      }
    } else {
      rv = mDocChangeRange->SetEnd(selNode, selOffset);
      rv = mDocChangeRange->CollapseTo(selNode, selOffset);
      if (NS_WARN_IF(NS_FAILED(rv))) {
        return rv;
      }
@@ -5250,10 +5249,11 @@ HTMLEditRules::ExpandSelectionForDeletion(Selection& aSelection)

    // Create a range that represents expanded selection
    RefPtr<nsRange> range = new nsRange(selStartNode);
    nsresult rv = range->SetStart(selStartNode, selStartOffset);
    NS_ENSURE_SUCCESS(rv, rv);
    rv = range->SetEnd(selEndNode, selEndOffset);
    NS_ENSURE_SUCCESS(rv, rv);
    nsresult rv = range->SetStartAndEnd(selStartNode, selStartOffset,
                                        selEndNode, selEndOffset);
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return rv;
    }

    // Check if block is entirely inside range
    if (brBlock) {
@@ -5714,26 +5714,27 @@ HTMLEditRules::PromoteRange(nsRange& aRange,
  // This is tricky.  The basic idea is to push out the range endpoints to
  // truly enclose the blocks that we will affect.

  nsCOMPtr<nsIDOMNode> opStartNode;
  nsCOMPtr<nsIDOMNode> opEndNode;
  nsCOMPtr<nsIDOMNode> opDOMStartNode;
  nsCOMPtr<nsIDOMNode> opDOMEndNode;
  int32_t opStartOffset, opEndOffset;

  GetPromotedPoint(kStart, GetAsDOMNode(startNode), startOffset,
                   aOperationType, address_of(opStartNode), &opStartOffset);
                   aOperationType, address_of(opDOMStartNode), &opStartOffset);
  GetPromotedPoint(kEnd, GetAsDOMNode(endNode), endOffset, aOperationType,
                   address_of(opEndNode), &opEndOffset);
                   address_of(opDOMEndNode), &opEndOffset);

  // Make sure that the new range ends up to be in the editable section.
  if (!htmlEditor->IsDescendantOfEditorRoot(
        EditorBase::GetNodeAtRangeOffsetPoint(opStartNode, opStartOffset)) ||
        EditorBase::GetNodeAtRangeOffsetPoint(opDOMStartNode, opStartOffset)) ||
      !htmlEditor->IsDescendantOfEditorRoot(
        EditorBase::GetNodeAtRangeOffsetPoint(opEndNode, opEndOffset - 1))) {
        EditorBase::GetNodeAtRangeOffsetPoint(opDOMEndNode, opEndOffset - 1))) {
    return;
  }

  DebugOnly<nsresult> rv = aRange.SetStart(opStartNode, opStartOffset);
  MOZ_ASSERT(NS_SUCCEEDED(rv));
  rv = aRange.SetEnd(opEndNode, opEndOffset);
  nsCOMPtr<nsINode> opStartNode = do_QueryInterface(opDOMStartNode);
  nsCOMPtr<nsINode> opEndNode = do_QueryInterface(opDOMEndNode);
  DebugOnly<nsresult> rv =
    aRange.SetStartAndEnd(opStartNode, opStartOffset, opEndNode, opEndOffset);
  MOZ_ASSERT(NS_SUCCEEDED(rv));
}

@@ -7400,10 +7401,10 @@ HTMLEditRules::PinSelectionToNewBlock(Selection* aSelection)
  nsCOMPtr<nsINode> node = do_QueryInterface(selNode);
  NS_ENSURE_STATE(node);
  RefPtr<nsRange> range = new nsRange(node);
  rv = range->SetStart(selNode, selOffset);
  NS_ENSURE_SUCCESS(rv, rv);
  rv = range->SetEnd(selNode, selOffset);
  NS_ENSURE_SUCCESS(rv, rv);
  rv = range->CollapseTo(node, selOffset);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }
  bool nodeBefore, nodeAfter;
  rv = nsRange::CompareNodeToRange(mNewBlock, range, &nodeBefore, &nodeAfter);
  NS_ENSURE_SUCCESS(rv, rv);
@@ -8298,10 +8299,13 @@ HTMLEditRules::DidSplitNode(nsIDOMNode* aExistingRightNode,
  if (!mListenerEnabled) {
    return NS_OK;
  }
  nsresult rv = mUtilRange->SetStart(aNewLeftNode, 0);
  NS_ENSURE_SUCCESS(rv, rv);
  rv = mUtilRange->SetEnd(aExistingRightNode, 0);
  NS_ENSURE_SUCCESS(rv, rv);
  nsCOMPtr<nsINode> newLeftNode = do_QueryInterface(aNewLeftNode);
  nsCOMPtr<nsINode> existingRightNode = do_QueryInterface(aExistingRightNode);
  nsresult rv = mUtilRange->SetStartAndEnd(newLeftNode, 0,
                                           existingRightNode, 0);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }
  return UpdateDocChangeRange(mUtilRange);
}

@@ -8326,11 +8330,12 @@ HTMLEditRules::DidJoinNodes(nsIDOMNode* aLeftNode,
  if (!mListenerEnabled) {
    return NS_OK;
  }
  nsCOMPtr<nsINode> rightNode = do_QueryInterface(aRightNode);
  // assumption that Join keeps the righthand node
  nsresult rv = mUtilRange->SetStart(aRightNode, mJoinOffset);
  NS_ENSURE_SUCCESS(rv, rv);
  rv = mUtilRange->SetEnd(aRightNode, mJoinOffset);
  NS_ENSURE_SUCCESS(rv, rv);
  nsresult rv = mUtilRange->CollapseTo(rightNode, mJoinOffset);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }
  return UpdateDocChangeRange(mUtilRange);
}

@@ -8352,11 +8357,12 @@ HTMLEditRules::DidInsertText(nsIDOMCharacterData* aTextNode,
    return NS_OK;
  }
  int32_t length = aString.Length();
  nsCOMPtr<nsIDOMNode> theNode = do_QueryInterface(aTextNode);
  nsresult rv = mUtilRange->SetStart(theNode, aOffset);
  NS_ENSURE_SUCCESS(rv, rv);
  rv = mUtilRange->SetEnd(theNode, aOffset+length);
  NS_ENSURE_SUCCESS(rv, rv);
  nsCOMPtr<nsINode> theNode = do_QueryInterface(aTextNode);
  nsresult rv = mUtilRange->SetStartAndEnd(theNode, aOffset,
                                           theNode, aOffset + length);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }
  return UpdateDocChangeRange(mUtilRange);
}

@@ -8377,11 +8383,11 @@ HTMLEditRules::DidDeleteText(nsIDOMCharacterData* aTextNode,
  if (!mListenerEnabled) {
    return NS_OK;
  }
  nsCOMPtr<nsIDOMNode> theNode = do_QueryInterface(aTextNode);
  nsresult rv = mUtilRange->SetStart(theNode, aOffset);
  NS_ENSURE_SUCCESS(rv, rv);
  rv = mUtilRange->SetEnd(theNode, aOffset);
  NS_ENSURE_SUCCESS(rv, rv);
  nsCOMPtr<nsINode> theNode = do_QueryInterface(aTextNode);
  nsresult rv = mUtilRange->CollapseTo(theNode, aOffset);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }
  return UpdateDocChangeRange(mUtilRange);
}

@@ -8396,20 +8402,25 @@ HTMLEditRules::WillDeleteSelection(nsISelection* aSelection)
  }
  RefPtr<Selection> selection = aSelection->AsSelection();
  // get the (collapsed) selection location
  nsCOMPtr<nsIDOMNode> selNode;
  int32_t selOffset;

  nsCOMPtr<nsINode> startNode;
  int32_t startOffset;
  nsresult rv =
    EditorBase::GetStartNodeAndOffset(selection,
                                      getter_AddRefs(selNode), &selOffset);
  NS_ENSURE_SUCCESS(rv, rv);
  rv = mUtilRange->SetStart(selNode, selOffset);
  NS_ENSURE_SUCCESS(rv, rv);
                                      getter_AddRefs(startNode), &startOffset);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }
  nsCOMPtr<nsINode> endNode;
  int32_t endOffset;
  rv = EditorBase::GetEndNodeAndOffset(selection,
                                       getter_AddRefs(selNode), &selOffset);
  NS_ENSURE_SUCCESS(rv, rv);
  rv = mUtilRange->SetEnd(selNode, selOffset);
  NS_ENSURE_SUCCESS(rv, rv);
                                       getter_AddRefs(endNode), &endOffset);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }
  rv = mUtilRange->SetStartAndEnd(startNode, startOffset, endNode, endOffset);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }
  return UpdateDocChangeRange(mUtilRange);
}

Loading