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
--- 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)
{