зеркало из https://github.com/mozilla/gecko-dev.git
Bug 348681 - Don't allow overlapping ranges in the selection. r+sr=roc
This commit is contained in:
Родитель
c77f3164bf
Коммит
7c815c938d
|
@ -159,15 +159,14 @@ struct CachedOffsetForFrame {
|
||||||
|
|
||||||
struct RangeData
|
struct RangeData
|
||||||
{
|
{
|
||||||
RangeData(nsIRange* aRange, PRInt32 aEndIndex) :
|
RangeData(nsIRange* aRange) :
|
||||||
mRange(aRange), mEndIndex(aEndIndex) {}
|
mRange(aRange) {}
|
||||||
|
|
||||||
nsCOMPtr<nsIRange> mRange;
|
nsCOMPtr<nsIRange> mRange;
|
||||||
PRInt32 mEndIndex; // index into mRangeEndings of this item
|
|
||||||
nsTextRangeStyle mTextRangeStyle;
|
nsTextRangeStyle mTextRangeStyle;
|
||||||
};
|
};
|
||||||
|
|
||||||
static RangeData sEmptyData(nsnull, 0);
|
static RangeData sEmptyData(nsnull);
|
||||||
|
|
||||||
// Note, the ownership of nsTypedSelection depends on which way the object is
|
// Note, the ownership of nsTypedSelection depends on which way the object is
|
||||||
// created. When nsFrameSelection has created nsTypedSelection,
|
// created. When nsFrameSelection has created nsTypedSelection,
|
||||||
|
@ -207,6 +206,8 @@ public:
|
||||||
PRBool aDoFlush,
|
PRBool aDoFlush,
|
||||||
PRInt16 aVPercent = NS_PRESSHELL_SCROLL_ANYWHERE,
|
PRInt16 aVPercent = NS_PRESSHELL_SCROLL_ANYWHERE,
|
||||||
PRInt16 aHPercent = NS_PRESSHELL_SCROLL_ANYWHERE);
|
PRInt16 aHPercent = NS_PRESSHELL_SCROLL_ANYWHERE);
|
||||||
|
nsresult SubtractRange(RangeData* aRange, nsIRange* aSubtract,
|
||||||
|
nsTArray<RangeData>* aOutput);
|
||||||
nsresult AddItem(nsIRange *aRange, PRInt32* aOutIndex = nsnull);
|
nsresult AddItem(nsIRange *aRange, PRInt32* aOutIndex = nsnull);
|
||||||
nsresult RemoveItem(nsIRange *aRange);
|
nsresult RemoveItem(nsIRange *aRange);
|
||||||
nsresult RemoveCollapsedRanges();
|
nsresult RemoveCollapsedRanges();
|
||||||
|
@ -305,63 +306,38 @@ private:
|
||||||
nsresult getTableCellLocationFromRange(nsIRange *aRange, PRInt32 *aSelectionType, PRInt32 *aRow, PRInt32 *aCol);
|
nsresult getTableCellLocationFromRange(nsIRange *aRange, PRInt32 *aSelectionType, PRInt32 *aRow, PRInt32 *aCol);
|
||||||
nsresult addTableCellRange(nsIRange *aRange, PRBool *aDidAddRange, PRInt32 *aOutIndex);
|
nsresult addTableCellRange(nsIRange *aRange, PRBool *aDidAddRange, PRInt32 *aOutIndex);
|
||||||
|
|
||||||
// These are the ranges inside this selection. They are kept sorted in order
|
|
||||||
// of DOM position of start and end, respectively (both of these arrays
|
|
||||||
// should have the same contents, but possibly in different orders).
|
|
||||||
//
|
|
||||||
// This data structure is sorted by the range beginnings and the range
|
|
||||||
// endings. When searching for a range, we can discard ranges whose ends
|
|
||||||
// are before the point in question, or whose beginnings are after. We can
|
|
||||||
// find these two sets of ranges on O(log n) time.
|
|
||||||
//
|
|
||||||
// Merging these two result sets takes O(n) time, so a a full query is O(log
|
|
||||||
// n + n) time. This looks worse than brute force O(n) searching, but in
|
|
||||||
// practice it is much faster. The DOM comparisons used in a brute force
|
|
||||||
// search are VERY expensive because they actually walk the DOM tree. We only
|
|
||||||
// do log(n) of these comparisons.
|
|
||||||
//
|
|
||||||
// Our O(n) merging step uses very fast integer comparisons, and, since
|
|
||||||
// we store the range ending index in the structure, we have to merge at
|
|
||||||
// most half of the results. Timing shows that this algorithm is nearly
|
|
||||||
// twice as fast doing intersections for 9 ranges, and 18 times faster for
|
|
||||||
// 250 ranges.
|
|
||||||
//
|
|
||||||
// An interval tree would give us O(log n) time lookups, which would be
|
|
||||||
// better. However, this approach gets us most of the way, and doesn't
|
|
||||||
// require rebalancing and other overhead. If this algorithm is found to be
|
|
||||||
// a bottleneck, it should be replaced with an interval tree.
|
|
||||||
//
|
|
||||||
// It has been discussed requiring selections to be disjoint in the future.
|
|
||||||
// If this requirement is added, the current design makes the most sense, we
|
|
||||||
// can just remove the array sorted by endings.
|
|
||||||
|
|
||||||
#ifdef DEBUG
|
|
||||||
PRBool ValidateRanges();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
PRInt32 FindInsertionPoint(
|
PRInt32 FindInsertionPoint(
|
||||||
const nsTArray<PRInt32>* aRemappingArray,
|
nsTArray<RangeData>* aElementArray,
|
||||||
nsINode* aPointNode, PRInt32 aPointOffset,
|
nsINode* aPointNode, PRInt32 aPointOffset,
|
||||||
PRInt32 (*aComparator)(nsINode*,PRInt32,nsIRange*));
|
PRInt32 (*aComparator)(nsINode*,PRInt32,nsIRange*));
|
||||||
void MoveIndexToFirstMatch(PRInt32* aIndex, nsINode* aNode,
|
PRBool EqualsRangeAtPoint(nsINode* aBeginNode, PRInt32 aBeginOffset,
|
||||||
PRInt32 aOffset,
|
|
||||||
const nsTArray<PRInt32>* aArray,
|
|
||||||
PRBool aUseBeginning);
|
|
||||||
void MoveIndexToNextMismatch(PRInt32* aIndex, nsINode* aNode,
|
|
||||||
PRInt32 aOffset,
|
|
||||||
const nsTArray<PRInt32>* aRemappingArray,
|
|
||||||
PRBool aUseBeginning);
|
|
||||||
PRInt32 FindRangeGivenPoint(nsINode* aBeginNode, PRInt32 aBeginOffset,
|
|
||||||
nsINode* aEndNode, PRInt32 aEndOffset,
|
nsINode* aEndNode, PRInt32 aEndOffset,
|
||||||
PRInt32 aStartSearchingHere);
|
PRInt32 aRangeIndex);
|
||||||
nsresult GetRangesForIntervalCOMArray(nsINode* aBeginNode, PRInt32 aBeginOffset,
|
nsresult GetRangesForIntervalCOMArray(nsINode* aBeginNode, PRInt32 aBeginOffset,
|
||||||
nsINode* aEndNode, PRInt32 aEndOffset,
|
nsINode* aEndNode, PRInt32 aEndOffset,
|
||||||
PRBool aAllowAdjacent,
|
PRBool aAllowAdjacent,
|
||||||
nsCOMArray<nsIRange>* aRanges);
|
nsCOMArray<nsIRange>* aRanges);
|
||||||
|
void GetIndicesForInterval(nsINode* aBeginNode, PRInt32 aBeginOffset,
|
||||||
|
nsINode* aEndNode, PRInt32 aEndOffset,
|
||||||
|
PRBool aAllowAdjacent,
|
||||||
|
PRInt32 *aStartIndex, PRInt32 *aEndIndex);
|
||||||
RangeData* FindRangeData(nsIDOMRange* aRange);
|
RangeData* FindRangeData(nsIDOMRange* aRange);
|
||||||
|
|
||||||
|
// These are the ranges inside this selection. They are kept sorted in order
|
||||||
|
// of DOM start position.
|
||||||
|
//
|
||||||
|
// This data structure is sorted by the range beginnings. As the ranges are
|
||||||
|
// disjoint, it is also implicitly sorted by the range endings. This allows
|
||||||
|
// us to perform binary searches when searching for existence of a range,
|
||||||
|
// giving us O(log n) search time.
|
||||||
|
//
|
||||||
|
// Inserting a new range requires finding the overlapping interval, requiring
|
||||||
|
// two binary searches plus up to an additional 6 DOM comparisons. If this
|
||||||
|
// proves to be a performance concern, then an interval tree may be a
|
||||||
|
// possible solution, allowing the calculation of the overlap interval in
|
||||||
|
// O(log n) time, though this would require rebalancing and other overhead.
|
||||||
nsTArray<RangeData> mRanges;
|
nsTArray<RangeData> mRanges;
|
||||||
nsTArray<PRInt32> mRangeEndings; // references info mRanges
|
|
||||||
nsCOMPtr<nsIRange> mAnchorFocusRange;
|
nsCOMPtr<nsIRange> mAnchorFocusRange;
|
||||||
nsRefPtr<nsFrameSelection> mFrameSelection;
|
nsRefPtr<nsFrameSelection> mFrameSelection;
|
||||||
nsWeakPtr mPresShellWeak;
|
nsWeakPtr mPresShellWeak;
|
||||||
|
@ -2143,7 +2119,7 @@ nsFrameSelection::GetFrameForNodeOffset(nsIContent *aNode,
|
||||||
|
|
||||||
#ifdef DONT_DO_THIS_YET
|
#ifdef DONT_DO_THIS_YET
|
||||||
// XXX: We can't use this code yet because the hinting
|
// XXX: We can't use this code yet because the hinting
|
||||||
// can cause us to attatch to the wrong line frame.
|
// can cause us to attach to the wrong line frame.
|
||||||
|
|
||||||
// Now that we have the child node, check if it too
|
// Now that we have the child node, check if it too
|
||||||
// can contain children. If so, call this method again!
|
// can contain children. If so, call this method again!
|
||||||
|
@ -3608,76 +3584,27 @@ CompareToRangeEnd(nsINode* aCompareNode, PRInt32 aCompareOffset,
|
||||||
aRange->EndOffset());
|
aRange->EndOffset());
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef DEBUG
|
|
||||||
// checks the indices to make sure the range bookkeeping is up-to-date, useful
|
|
||||||
// for debugging to make sure everything is consistent
|
|
||||||
PRBool
|
|
||||||
nsTypedSelection::ValidateRanges()
|
|
||||||
{
|
|
||||||
if (mRanges.Length() != mRangeEndings.Length()) {
|
|
||||||
NS_NOTREACHED("Lengths don't match");
|
|
||||||
return PR_FALSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
// check the main array and make sure they refer to the correct sorted ones
|
|
||||||
PRUint32 i;
|
|
||||||
for (i = 0; i < mRanges.Length(); i ++) {
|
|
||||||
if (mRanges[i].mEndIndex < 0 ||
|
|
||||||
mRanges[i].mEndIndex >= (PRInt32)mRangeEndings.Length()) {
|
|
||||||
NS_NOTREACHED("Sorted index is out of bounds");
|
|
||||||
return PR_FALSE;
|
|
||||||
}
|
|
||||||
if (mRangeEndings[mRanges[i].mEndIndex] != (PRInt32)i) {
|
|
||||||
NS_NOTREACHED("Beginning or ending isn't correct");
|
|
||||||
return PR_FALSE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// check the other array to make sure they all refer to valid indices
|
|
||||||
for (i = 0; i < mRangeEndings.Length(); i ++) {
|
|
||||||
if (mRangeEndings[i] < 0 ||
|
|
||||||
mRangeEndings[i] >= (PRInt32)mRanges.Length()) {
|
|
||||||
NS_NOTREACHED("Ending refers to invalid index");
|
|
||||||
return PR_FALSE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return PR_TRUE;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// nsTypedSelection::FindInsertionPoint
|
// nsTypedSelection::FindInsertionPoint
|
||||||
//
|
//
|
||||||
// Binary searches the sorted array of ranges for the insertion point for
|
// Binary searches the given sorted array of ranges for the insertion point
|
||||||
// the given node/offset. The given comparator is used, and the index where
|
// for the given node/offset. The given comparator is used, and the index
|
||||||
// the point should appear in the array is placed in *aInsertionPoint;
|
// where the point should appear in the array is placed in *aInsertionPoint.
|
||||||
//
|
//
|
||||||
// If the remapping array is given, we'll find the position in that array
|
// If there is an item in the array equal to the input point, we will return
|
||||||
// for the insertion, where that array contains indices that reference
|
// the index of this item.
|
||||||
// the range array. This can be NULL to not use a remapping array.
|
|
||||||
//
|
|
||||||
// If there is item(s) in the array equal to the input point, we will return
|
|
||||||
// a random index between or adjacent to this item(s).
|
|
||||||
|
|
||||||
PRInt32
|
PRInt32
|
||||||
nsTypedSelection::FindInsertionPoint(
|
nsTypedSelection::FindInsertionPoint(
|
||||||
const nsTArray<PRInt32>* aRemappingArray,
|
nsTArray<RangeData>* aElementArray,
|
||||||
nsINode* aPointNode, PRInt32 aPointOffset,
|
nsINode* aPointNode, PRInt32 aPointOffset,
|
||||||
PRInt32 (*aComparator)(nsINode*,PRInt32,nsIRange*))
|
PRInt32 (*aComparator)(nsINode*,PRInt32,nsIRange*))
|
||||||
{
|
{
|
||||||
NS_ASSERTION(!aRemappingArray || aRemappingArray->Length() == mRanges.Length(),
|
|
||||||
"Remapping array must have the same entries as the range array");
|
|
||||||
|
|
||||||
PRInt32 beginSearch = 0;
|
PRInt32 beginSearch = 0;
|
||||||
PRInt32 endSearch = mRanges.Length(); // one beyond what to check
|
PRInt32 endSearch = aElementArray->Length(); // one beyond what to check
|
||||||
while (endSearch - beginSearch > 0) {
|
while (endSearch - beginSearch > 0) {
|
||||||
PRInt32 center = (endSearch - beginSearch) / 2 + beginSearch;
|
PRInt32 center = (endSearch - beginSearch) / 2 + beginSearch;
|
||||||
|
|
||||||
nsIRange* range;
|
nsIRange* range = (*aElementArray)[center].mRange;
|
||||||
if (aRemappingArray)
|
|
||||||
range = mRanges[(*aRemappingArray)[center]].mRange;
|
|
||||||
else
|
|
||||||
range = mRanges[center].mRange;
|
|
||||||
|
|
||||||
PRInt32 cmp = aComparator(aPointNode, aPointOffset, range);
|
PRInt32 cmp = aComparator(aPointNode, aPointOffset, range);
|
||||||
|
|
||||||
|
@ -3693,6 +3620,77 @@ nsTypedSelection::FindInsertionPoint(
|
||||||
return beginSearch;
|
return beginSearch;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// nsTypedSelection::SubtractRange
|
||||||
|
//
|
||||||
|
// A helper function that subtracts aSubtract from aRange, and adds
|
||||||
|
// 0 or 1 RangeData objects representing the remaining non-overlapping
|
||||||
|
// difference to aOutput
|
||||||
|
|
||||||
|
nsresult
|
||||||
|
nsTypedSelection::SubtractRange(RangeData* aRange, nsIRange* aSubtract,
|
||||||
|
nsTArray<RangeData>* aOutput)
|
||||||
|
{
|
||||||
|
nsIRange* range = aRange->mRange;
|
||||||
|
|
||||||
|
// First we want to compare to the range start
|
||||||
|
PRInt32 cmp = CompareToRangeStart(range->GetStartParent(),
|
||||||
|
range->StartOffset(),
|
||||||
|
aSubtract);
|
||||||
|
|
||||||
|
// Also, make a comparison to the range end
|
||||||
|
PRInt32 cmp2 = CompareToRangeEnd(range->GetEndParent(),
|
||||||
|
range->EndOffset(),
|
||||||
|
aSubtract);
|
||||||
|
|
||||||
|
// If the existing range left overlaps the new range (aSubtract) then
|
||||||
|
// cmp < 0, and cmp2 < 0
|
||||||
|
// If it right overlaps the new range then cmp > 0 and cmp2 > 0
|
||||||
|
// If it fully contains the new range, then cmp < 0 and cmp2 > 0
|
||||||
|
|
||||||
|
if (cmp2 > 0) {
|
||||||
|
// We need to add a new RangeData to the output, running from
|
||||||
|
// the end of aSubtract to the end of range
|
||||||
|
nsIRange* postOverlap = new nsRange();
|
||||||
|
if (!postOverlap)
|
||||||
|
return NS_ERROR_OUT_OF_MEMORY;
|
||||||
|
|
||||||
|
nsresult rv =
|
||||||
|
postOverlap->SetStart(aSubtract->GetEndParent(), aSubtract->EndOffset());
|
||||||
|
NS_ENSURE_SUCCESS(rv, rv);
|
||||||
|
rv =
|
||||||
|
postOverlap->SetEnd(range->GetEndParent(), range->EndOffset());
|
||||||
|
NS_ENSURE_SUCCESS(rv, rv);
|
||||||
|
if (!postOverlap->Collapsed()) {
|
||||||
|
if (!aOutput->InsertElementAt(0, RangeData(postOverlap)))
|
||||||
|
return NS_ERROR_OUT_OF_MEMORY;
|
||||||
|
(*aOutput)[0].mTextRangeStyle = aRange->mTextRangeStyle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cmp < 0) {
|
||||||
|
// We need to add a new RangeData to the output, running from
|
||||||
|
// the start of the range to the start of aSubtract
|
||||||
|
nsIRange* preOverlap = new nsRange();
|
||||||
|
if (!preOverlap)
|
||||||
|
return NS_ERROR_OUT_OF_MEMORY;
|
||||||
|
|
||||||
|
nsresult rv =
|
||||||
|
preOverlap->SetStart(range->GetStartParent(), range->StartOffset());
|
||||||
|
NS_ENSURE_SUCCESS(rv, rv);
|
||||||
|
rv =
|
||||||
|
preOverlap->SetEnd(aSubtract->GetStartParent(), aSubtract->StartOffset());
|
||||||
|
NS_ENSURE_SUCCESS(rv, rv);
|
||||||
|
|
||||||
|
if (!preOverlap->Collapsed()) {
|
||||||
|
if (!aOutput->InsertElementAt(0, RangeData(preOverlap)))
|
||||||
|
return NS_ERROR_OUT_OF_MEMORY;
|
||||||
|
(*aOutput)[0].mTextRangeStyle = aRange->mTextRangeStyle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return NS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
nsresult
|
nsresult
|
||||||
nsTypedSelection::AddItem(nsIRange *aItem, PRInt32 *aOutIndex)
|
nsTypedSelection::AddItem(nsIRange *aItem, PRInt32 *aOutIndex)
|
||||||
{
|
{
|
||||||
|
@ -3703,87 +3701,95 @@ nsTypedSelection::AddItem(nsIRange *aItem, PRInt32 *aOutIndex)
|
||||||
if (aOutIndex)
|
if (aOutIndex)
|
||||||
*aOutIndex = -1;
|
*aOutIndex = -1;
|
||||||
|
|
||||||
NS_ASSERTION(ValidateRanges(), "Ranges out of sync");
|
|
||||||
|
|
||||||
// a common case is that we have no ranges yet
|
// a common case is that we have no ranges yet
|
||||||
if (mRanges.Length() == 0) {
|
if (mRanges.Length() == 0) {
|
||||||
if (! mRanges.AppendElement(RangeData(aItem, 0)))
|
if (!mRanges.AppendElement(RangeData(aItem)))
|
||||||
return NS_ERROR_OUT_OF_MEMORY;
|
return NS_ERROR_OUT_OF_MEMORY;
|
||||||
if (! mRangeEndings.AppendElement(0)) {
|
|
||||||
mRanges.Clear();
|
|
||||||
return NS_ERROR_OUT_OF_MEMORY;
|
|
||||||
}
|
|
||||||
if (aOutIndex)
|
if (aOutIndex)
|
||||||
*aOutIndex = 0;
|
*aOutIndex = 0;
|
||||||
return NS_OK;
|
return NS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
PRInt32 beginInsertionPoint =
|
PRInt32 startIndex, endIndex;
|
||||||
FindInsertionPoint(nsnull, aItem->GetStartParent(), aItem->StartOffset(),
|
GetIndicesForInterval(aItem->GetStartParent(), aItem->StartOffset(),
|
||||||
CompareToRangeStart);
|
aItem->GetEndParent(), aItem->EndOffset(),
|
||||||
|
PR_FALSE, &startIndex, &endIndex);
|
||||||
|
|
||||||
|
if (endIndex == -1) {
|
||||||
|
// All ranges start after the given range. We can insert our range at
|
||||||
|
// position 0, knowing there are no overlaps (handled below)
|
||||||
|
startIndex = 0;
|
||||||
|
endIndex = 0;
|
||||||
|
} else if (startIndex == -1) {
|
||||||
|
// All ranges end before the given range. We can insert our range at
|
||||||
|
// the end of the array, knowing there are no overlaps (handled below)
|
||||||
|
startIndex = mRanges.Length();
|
||||||
|
endIndex = endIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (startIndex == endIndex) {
|
||||||
|
// The new range doesn't overlap any existing ranges
|
||||||
|
if (!mRanges.InsertElementAt(startIndex, RangeData(aItem)))
|
||||||
|
return NS_ERROR_OUT_OF_MEMORY;
|
||||||
if (aOutIndex)
|
if (aOutIndex)
|
||||||
*aOutIndex = beginInsertionPoint;
|
*aOutIndex = startIndex;
|
||||||
|
return NS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
// XXX PERFORMANCE: 99% of the time, the beginning array and the ending array
|
// If the range is already contained in mRanges, silently succeed
|
||||||
// will be the same because the ranges do not overlap. We could save a few
|
PRBool sameRange = EqualsRangeAtPoint(aItem->GetStartParent(),
|
||||||
// compares (which can be expensive) in this common case by special casing
|
|
||||||
// this.
|
|
||||||
|
|
||||||
// make sure that this range is not already in the selection
|
|
||||||
PRInt32 index = FindRangeGivenPoint(aItem->GetStartParent(),
|
|
||||||
aItem->StartOffset(),
|
aItem->StartOffset(),
|
||||||
aItem->GetEndParent(),
|
aItem->GetEndParent(),
|
||||||
aItem->EndOffset(),
|
aItem->EndOffset(), startIndex);
|
||||||
beginInsertionPoint);
|
if (sameRange) {
|
||||||
if (index >= 0) {
|
*aOutIndex = startIndex;
|
||||||
// silently succeed, this range is already in the selection
|
|
||||||
if (aOutIndex)
|
|
||||||
*aOutIndex = index;
|
|
||||||
return NS_OK;
|
return NS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
PRInt32 endInsertionPoint =
|
// We now know that at least 1 existing range overlaps with the range that
|
||||||
FindInsertionPoint(&mRangeEndings, aItem->GetEndParent(),
|
// we are trying to add. In fact, the only ranges of interest are those at
|
||||||
aItem->EndOffset(), CompareToRangeEnd);
|
// the two end points, startIndex and endIndex - 1 (which may point to the
|
||||||
|
// same range) as these may partially overlap the new range. Any ranges
|
||||||
// insert the range, being careful to revert everything on error to keep
|
// between these indices are fully overlapped by the new range, and so can be
|
||||||
// consistency
|
// removed
|
||||||
if (! mRanges.InsertElementAt(beginInsertionPoint,
|
nsTArray<RangeData> overlaps;
|
||||||
RangeData(aItem, endInsertionPoint))) {
|
if (!overlaps.InsertElementAt(0, mRanges[startIndex]))
|
||||||
return NS_ERROR_OUT_OF_MEMORY;
|
return NS_ERROR_OUT_OF_MEMORY;
|
||||||
}
|
|
||||||
if (! mRangeEndings.InsertElementAt(endInsertionPoint, beginInsertionPoint)) {
|
if (endIndex - 1 != startIndex) {
|
||||||
mRanges.RemoveElementAt(beginInsertionPoint);
|
if (!overlaps.InsertElementAt(1, mRanges[endIndex - 1]))
|
||||||
return NS_ERROR_OUT_OF_MEMORY;
|
return NS_ERROR_OUT_OF_MEMORY;
|
||||||
}
|
}
|
||||||
|
|
||||||
// adjust the end indices that point to the main list
|
// Remove all the overlapping ranges
|
||||||
PRUint32 i;
|
mRanges.RemoveElementsAt(startIndex, endIndex - startIndex);
|
||||||
for (i = 0; i < mRangeEndings.Length(); i ++) {
|
|
||||||
if (mRangeEndings[i] >= beginInsertionPoint)
|
nsTArray<RangeData> temp;
|
||||||
mRangeEndings[i] ++;
|
for (PRInt32 i = overlaps.Length() - 1; i >= 0; i--) {
|
||||||
|
nsresult rv = SubtractRange(&overlaps[i], aItem, &temp);
|
||||||
|
NS_ENSURE_SUCCESS(rv, rv);
|
||||||
}
|
}
|
||||||
|
|
||||||
// the last loop updated the inserted index as well, so we need to put it
|
// Insert the new element into our "leftovers" array
|
||||||
// back (this saves a comparison in that loop)
|
PRInt32 insertionPoint = FindInsertionPoint(&temp, aItem->GetStartParent(),
|
||||||
mRangeEndings[endInsertionPoint] = beginInsertionPoint;
|
aItem->StartOffset(),
|
||||||
|
CompareToRangeStart);
|
||||||
|
|
||||||
// adjust the begin/end indices
|
if (!temp.InsertElementAt(insertionPoint, RangeData(aItem)))
|
||||||
for (i = endInsertionPoint + 1; i < mRangeEndings.Length(); i ++)
|
return NS_ERROR_OUT_OF_MEMORY;
|
||||||
mRanges[mRangeEndings[i]].mEndIndex = i;
|
|
||||||
|
// Merge the leftovers back in to mRanges
|
||||||
|
if (!mRanges.InsertElementsAt(startIndex, temp))
|
||||||
|
return NS_ERROR_OUT_OF_MEMORY;
|
||||||
|
|
||||||
NS_ASSERTION(ValidateRanges(), "Ranges out of sync");
|
|
||||||
return NS_OK;
|
return NS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
nsresult
|
nsresult
|
||||||
nsTypedSelection::RemoveItem(nsIRange *aItem)
|
nsTypedSelection::RemoveItem(nsIRange *aItem)
|
||||||
{
|
{
|
||||||
if (!aItem)
|
if (!aItem)
|
||||||
return NS_ERROR_NULL_POINTER;
|
return NS_ERROR_NULL_POINTER;
|
||||||
NS_ASSERTION(ValidateRanges(), "Ranges out of sync");
|
|
||||||
|
|
||||||
// Find the range's index & remove it. We could use FindInsertionPoint to
|
// Find the range's index & remove it. We could use FindInsertionPoint to
|
||||||
// get O(log n) time, but that requires many expensive DOM comparisons.
|
// get O(log n) time, but that requires many expensive DOM comparisons.
|
||||||
|
@ -3799,27 +3805,8 @@ nsTypedSelection::RemoveItem(nsIRange *aItem)
|
||||||
}
|
}
|
||||||
if (idx < 0)
|
if (idx < 0)
|
||||||
return NS_ERROR_INVALID_ARG;
|
return NS_ERROR_INVALID_ARG;
|
||||||
|
|
||||||
mRanges.RemoveElementAt(idx);
|
mRanges.RemoveElementAt(idx);
|
||||||
|
|
||||||
// need to update the range ending list to reflect the removed item
|
|
||||||
PRInt32 endingIndex = -1;
|
|
||||||
for (i = 0; i < mRangeEndings.Length(); i ++) {
|
|
||||||
if (mRangeEndings[i] == idx)
|
|
||||||
endingIndex = i;
|
|
||||||
if (mRangeEndings[i] > idx)
|
|
||||||
mRangeEndings[i] --;
|
|
||||||
}
|
|
||||||
NS_ASSERTION(endingIndex >= 0 && endingIndex < (PRInt32)mRangeEndings.Length(),
|
|
||||||
"Index not found in ending list");
|
|
||||||
|
|
||||||
// remove from the sorted lists
|
|
||||||
mRangeEndings.RemoveElementAt(endingIndex);
|
|
||||||
|
|
||||||
// adjust indices of the RangeData structures
|
|
||||||
for (i = endingIndex; i < mRangeEndings.Length(); i ++)
|
|
||||||
mRanges[mRangeEndings[i]].mEndIndex = i;
|
|
||||||
|
|
||||||
NS_ASSERTION(ValidateRanges(), "Ranges out of sync");
|
|
||||||
return NS_OK;
|
return NS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3847,7 +3834,6 @@ nsTypedSelection::Clear(nsPresContext* aPresContext)
|
||||||
selectFrames(aPresContext, mRanges[i].mRange, 0);
|
selectFrames(aPresContext, mRanges[i].mRange, 0);
|
||||||
}
|
}
|
||||||
mRanges.Clear();
|
mRanges.Clear();
|
||||||
mRangeEndings.Clear();
|
|
||||||
|
|
||||||
// Reset direction so for more dependable table selection range handling
|
// Reset direction so for more dependable table selection range handling
|
||||||
SetDirection(eDirNext);
|
SetDirection(eDirNext);
|
||||||
|
@ -3862,116 +3848,6 @@ nsTypedSelection::Clear(nsPresContext* aPresContext)
|
||||||
return NS_OK;
|
return NS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
// nsTypedSelection::MoveIndexToFirstMatch
|
|
||||||
//
|
|
||||||
// This adjusts the given index backwards in the range array so that it
|
|
||||||
// points to the first match of (node,offset). There might be any number
|
|
||||||
// of identical sequencial items in the array and the index can initially
|
|
||||||
// point to any one of them. When complete, the index will be moved to
|
|
||||||
// point to the first one of these. If the index does not point to a
|
|
||||||
// match of (node,offset) or there is only one match, the index will be
|
|
||||||
// untouched.
|
|
||||||
//
|
|
||||||
// If there are multiple ranges beginning at the requested position, we'll
|
|
||||||
// get a random index in between those from FindInsertionPoint. We want the
|
|
||||||
// index to be at the first match in the array.
|
|
||||||
//
|
|
||||||
// If the remapping array (sorted list containing indices into mRanges)
|
|
||||||
// is given, we'll use that for sorting and consider the index into
|
|
||||||
// that array. If NULL, we'll just use mRanges directly.
|
|
||||||
//
|
|
||||||
// If aUseBeginning is set we'll compare to the range beginnings in the
|
|
||||||
// selection. If false, we'll compare to range endings.
|
|
||||||
|
|
||||||
void
|
|
||||||
nsTypedSelection::MoveIndexToFirstMatch(PRInt32* aIndex, nsINode* aNode,
|
|
||||||
PRInt32 aOffset,
|
|
||||||
const nsTArray<PRInt32>* aRemappingArray,
|
|
||||||
PRBool aUseBeginning)
|
|
||||||
{
|
|
||||||
while (*aIndex > 0) {
|
|
||||||
nsIRange* range;
|
|
||||||
if (aRemappingArray)
|
|
||||||
range = mRanges[(*aRemappingArray)[(*aIndex) - 1]].mRange;
|
|
||||||
else
|
|
||||||
range = mRanges[(*aIndex) - 1].mRange;
|
|
||||||
|
|
||||||
nsINode* curNode;
|
|
||||||
PRInt32 curOffset;
|
|
||||||
if (aUseBeginning) {
|
|
||||||
curNode = range->GetStartParent();
|
|
||||||
curOffset = range->StartOffset();
|
|
||||||
} else {
|
|
||||||
curNode = range->GetEndParent();
|
|
||||||
curOffset = range->EndOffset();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (curNode != aNode)
|
|
||||||
break; // not a match
|
|
||||||
if (curOffset != aOffset)
|
|
||||||
break; // not a match
|
|
||||||
|
|
||||||
// the previous node matches, go back one
|
|
||||||
(*aIndex) --;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// nsTypedSelection::MoveIndexToNextMismatch
|
|
||||||
//
|
|
||||||
// The same as MoveIndexToFirstMatch but increments the index until it
|
|
||||||
// points to something that doesn't match (node,offset).
|
|
||||||
//
|
|
||||||
// If there is one or more range ending at the requested position, we
|
|
||||||
// want to start checking the first one that ends inside the range. This
|
|
||||||
// moves the given index to point to the first index inside the range.
|
|
||||||
// It may be one past the last item in the array.
|
|
||||||
//
|
|
||||||
// If the requested DOM position is '9', and the caller got index 3
|
|
||||||
// from FindInsertionPoint with points in the range array:
|
|
||||||
// 6 8 9 9 9 13 19
|
|
||||||
// ^
|
|
||||||
// input
|
|
||||||
// The input point will point to a random '9' in the array, or possibly
|
|
||||||
// the '13', which are all valid points for a new '9' to live in this array.
|
|
||||||
// This function will move the index to point to the '13' in this case,
|
|
||||||
// which is the first offset not matching the input position.
|
|
||||||
//
|
|
||||||
// See MoveIndexToFirstMatch for the meaning of aRemappingArray and
|
|
||||||
// aUseBeginning.
|
|
||||||
|
|
||||||
void
|
|
||||||
nsTypedSelection::MoveIndexToNextMismatch(PRInt32* aIndex, nsINode* aNode,
|
|
||||||
PRInt32 aOffset,
|
|
||||||
const nsTArray<PRInt32>* aRemappingArray,
|
|
||||||
PRBool aUseBeginning)
|
|
||||||
{
|
|
||||||
while (*aIndex < (PRInt32)mRanges.Length()) {
|
|
||||||
nsIRange* range;
|
|
||||||
if (aRemappingArray)
|
|
||||||
range = mRanges[(*aRemappingArray)[*aIndex]].mRange;
|
|
||||||
else
|
|
||||||
range = mRanges[*aIndex].mRange;
|
|
||||||
|
|
||||||
nsINode* curNode;
|
|
||||||
PRInt32 curOffset;
|
|
||||||
if (aUseBeginning) {
|
|
||||||
curNode = range->GetStartParent();
|
|
||||||
curOffset = range->StartOffset();
|
|
||||||
} else {
|
|
||||||
curNode = range->GetEndParent();
|
|
||||||
curOffset = range->EndOffset();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (curNode != aNode)
|
|
||||||
break; // mismatch
|
|
||||||
if (curOffset != aOffset)
|
|
||||||
break; // mismatch
|
|
||||||
|
|
||||||
// this node matches, go to the next one
|
|
||||||
(*aIndex) ++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
NS_IMETHODIMP
|
NS_IMETHODIMP
|
||||||
nsTypedSelection::GetType(PRInt16 *aType)
|
nsTypedSelection::GetType(PRInt16 *aType)
|
||||||
{
|
{
|
||||||
|
@ -4046,85 +3922,114 @@ nsTypedSelection::GetRangesForIntervalCOMArray(nsIDOMNode* aBeginNode, PRInt32 a
|
||||||
return NS_OK;
|
return NS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// nsTypedSelection::GetRangesForIntervalCOMArray
|
||||||
|
//
|
||||||
|
// Fills a COM array with the ranges overlapping the range specified by
|
||||||
|
// the given endpoints. Ranges in the selection exactly adjacent to the
|
||||||
|
// input range are not returned unless aAllowAdjacent is set.
|
||||||
|
//
|
||||||
|
// For example, if the following ranges were in the selection
|
||||||
|
// (assume everything is within the same node)
|
||||||
|
//
|
||||||
|
// Start Offset: 0 2 7 9
|
||||||
|
// End Offset: 2 5 9 10
|
||||||
|
//
|
||||||
|
// and passed aBeginOffset of 2 and aEndOffset of 9, then with
|
||||||
|
// aAllowAdjacent set, all the ranges should be returned. If
|
||||||
|
// aAllowAdjacent was false, the ranges [2, 5] and [7, 9] only
|
||||||
|
// should be returned
|
||||||
|
//
|
||||||
|
// Now that overlapping ranges are disallowed, there can be a maximum of
|
||||||
|
// 2 adjacent ranges
|
||||||
|
|
||||||
nsresult
|
nsresult
|
||||||
nsTypedSelection::GetRangesForIntervalCOMArray(nsINode* aBeginNode, PRInt32 aBeginOffset,
|
nsTypedSelection::GetRangesForIntervalCOMArray(nsINode* aBeginNode, PRInt32 aBeginOffset,
|
||||||
nsINode* aEndNode, PRInt32 aEndOffset,
|
nsINode* aEndNode, PRInt32 aEndOffset,
|
||||||
PRBool aAllowAdjacent,
|
PRBool aAllowAdjacent,
|
||||||
nsCOMArray<nsIRange>* aRanges)
|
nsCOMArray<nsIRange>* aRanges)
|
||||||
{
|
{
|
||||||
NS_ASSERTION(ValidateRanges(), "Ranges out of sync");
|
|
||||||
aRanges->Clear();
|
aRanges->Clear();
|
||||||
if (mRanges.Length() == 0)
|
PRInt32 startIndex, endIndex;
|
||||||
|
GetIndicesForInterval(aBeginNode, aBeginOffset, aEndNode, aEndOffset,
|
||||||
|
aAllowAdjacent, &startIndex, &endIndex);
|
||||||
|
if (startIndex == -1 || endIndex == -1)
|
||||||
return NS_OK;
|
return NS_OK;
|
||||||
|
|
||||||
// Ranges that begin after the checked range, and ranges that end before
|
for (PRInt32 i = startIndex; i < endIndex; i++) {
|
||||||
// the checked range can be discarded. The beginning index is the offset
|
|
||||||
// into the beginning array of the first item we DON'T have to check.
|
|
||||||
|
|
||||||
// ...index into the beginning array that is the FIRST ITEM OUTSIDE
|
|
||||||
// OUR RANGE
|
|
||||||
PRInt32 beginningIndex =
|
|
||||||
FindInsertionPoint(nsnull, aEndNode, aEndOffset, &CompareToRangeStart);
|
|
||||||
if (beginningIndex == 0)
|
|
||||||
return NS_OK; // optimization: all ranges are after us
|
|
||||||
|
|
||||||
// ...index into the ending array that is the FIRST ITEM WE WANT TO CHECK
|
|
||||||
PRInt32 endingIndex =
|
|
||||||
FindInsertionPoint(&mRangeEndings, aBeginNode, aBeginOffset,
|
|
||||||
&CompareToRangeEnd);
|
|
||||||
if (endingIndex == (PRInt32)mRangeEndings.Length())
|
|
||||||
return NS_OK; // optimization: all ranges are before us
|
|
||||||
|
|
||||||
// adjust the indices in case of exact matches, FindInsertionPoint will
|
|
||||||
// give us a random index that would still be sorted if there is a match
|
|
||||||
if (aAllowAdjacent) {
|
|
||||||
// include adjacent points
|
|
||||||
//
|
|
||||||
// Recall that we want all things in mRangeEndings (indexed by endingIndex)
|
|
||||||
// before the requested beginning, and everything in mRanges (indexed by
|
|
||||||
// beginningIndex) after the requested ending.
|
|
||||||
//
|
|
||||||
// 1 3 5 5 5 8 9 10 10 10 11 12 <-- imaginary DOM positions of ranges
|
|
||||||
// ^ ^ <-- we have this
|
|
||||||
// ^ ^ <-- compute this range
|
|
||||||
// endingIndex beginningIndex
|
|
||||||
MoveIndexToFirstMatch(&endingIndex, aBeginNode, aBeginOffset,
|
|
||||||
&mRangeEndings, PR_FALSE);
|
|
||||||
MoveIndexToNextMismatch(&beginningIndex, aEndNode, aEndOffset,
|
|
||||||
nsnull, PR_TRUE);
|
|
||||||
} else {
|
|
||||||
// exclude adjacent points, see previous case
|
|
||||||
// 1 3 5 5 5 8 9 10 10 10 11 12 <-- imaginary DOM positions of ranges
|
|
||||||
// ^ ^ <-- we have this
|
|
||||||
// ^ ^ <-- compute this range
|
|
||||||
// endingIndex beginningIndex
|
|
||||||
MoveIndexToNextMismatch(&endingIndex, aBeginNode, aBeginOffset,
|
|
||||||
&mRangeEndings, PR_FALSE);
|
|
||||||
MoveIndexToFirstMatch(&beginningIndex, aEndNode, aEndOffset,
|
|
||||||
nsnull, PR_TRUE);
|
|
||||||
}
|
|
||||||
|
|
||||||
// check for overlaps in the two ranges by linearly searching the smallest
|
|
||||||
// set of matches
|
|
||||||
if (beginningIndex > (PRInt32)mRangeEndings.Length() - endingIndex) {
|
|
||||||
// check ending array because its smaller
|
|
||||||
for (PRInt32 i = endingIndex; i < (PRInt32)mRangeEndings.Length(); i ++) {
|
|
||||||
if (mRangeEndings[i] < beginningIndex) {
|
|
||||||
if (! aRanges->AppendObject(mRanges[mRangeEndings[i]].mRange))
|
|
||||||
return NS_ERROR_OUT_OF_MEMORY;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for (PRInt32 i = 0; i < beginningIndex; i ++) {
|
|
||||||
if (mRanges[i].mEndIndex >= endingIndex) {
|
|
||||||
if (!aRanges->AppendObject(mRanges[i].mRange))
|
if (!aRanges->AppendObject(mRanges[i].mRange))
|
||||||
return NS_ERROR_OUT_OF_MEMORY;
|
return NS_ERROR_OUT_OF_MEMORY;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
return NS_OK;
|
return NS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// nsTypedSelection::GetIndicesForInterval
|
||||||
|
//
|
||||||
|
// Works on the same principle as GetRangesForIntervalCOMArray above, however
|
||||||
|
// instead this returns the indices into mRanges between which the
|
||||||
|
// overlapping ranges lie.
|
||||||
|
|
||||||
|
void
|
||||||
|
nsTypedSelection::GetIndicesForInterval(nsINode* aBeginNode,
|
||||||
|
PRInt32 aBeginOffset,
|
||||||
|
nsINode* aEndNode, PRInt32 aEndOffset,
|
||||||
|
PRBool aAllowAdjacent,
|
||||||
|
PRInt32 *aStartIndex,
|
||||||
|
PRInt32 *aEndIndex)
|
||||||
|
{
|
||||||
|
if (aStartIndex)
|
||||||
|
*aStartIndex = -1;
|
||||||
|
if (aEndIndex)
|
||||||
|
*aEndIndex = -1;
|
||||||
|
|
||||||
|
if (mRanges.Length() == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Ranges that end before the given interval and begin after the given
|
||||||
|
// interval can be discarded
|
||||||
|
PRInt32 endsBeforeIndex =
|
||||||
|
FindInsertionPoint(&mRanges, aEndNode, aEndOffset, &CompareToRangeStart);
|
||||||
|
if (endsBeforeIndex == 0)
|
||||||
|
return; // optimization: all ranges are after us
|
||||||
|
*aEndIndex = endsBeforeIndex;
|
||||||
|
|
||||||
|
PRInt32 beginsAfterIndex =
|
||||||
|
FindInsertionPoint(&mRanges, aBeginNode, aBeginOffset, &CompareToRangeEnd);
|
||||||
|
if (beginsAfterIndex == (PRInt32) mRanges.Length())
|
||||||
|
return; // optimization: all ranges are before us
|
||||||
|
|
||||||
|
if (aAllowAdjacent) {
|
||||||
|
// If there is a range that starts on aEndNode, aEndOffset, then
|
||||||
|
// endsBeforeIndex will point to it (there can be only 1 such range),
|
||||||
|
// so increment endsBeforeIndex to encompass that range
|
||||||
|
if (endsBeforeIndex < mRanges.Length()) {
|
||||||
|
nsINode* endNode = mRanges[endsBeforeIndex].mRange->GetStartParent();
|
||||||
|
PRInt32 endOffset = mRanges[endsBeforeIndex].mRange->StartOffset();
|
||||||
|
if (endNode == aEndNode && endOffset == aEndOffset)
|
||||||
|
endsBeforeIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Likewise, if there is a range that ends on aStartNode, aStartOffset
|
||||||
|
// then beginsAfterIndex will already point to it, and doesn't need
|
||||||
|
// altered
|
||||||
|
} else {
|
||||||
|
// If there is a range that ends on aStartNode, aStartOffset then
|
||||||
|
// beginsAfterIndex will point to it, so increment it to exclude the range
|
||||||
|
nsINode* startNode = mRanges[beginsAfterIndex].mRange->GetEndParent();
|
||||||
|
PRInt32 startOffset = mRanges[beginsAfterIndex].mRange->EndOffset();
|
||||||
|
if (startNode == aBeginNode && startOffset == aBeginOffset)
|
||||||
|
beginsAfterIndex++;
|
||||||
|
|
||||||
|
// Likewise, if there is a range that starts on aEndNode, aEndOffset
|
||||||
|
// then endsBeforeIndex will already point to it, and doesn't need
|
||||||
|
// altered
|
||||||
|
}
|
||||||
|
|
||||||
|
*aStartIndex = beginsAfterIndex;
|
||||||
|
*aEndIndex = endsBeforeIndex;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// RangeMatches*Point
|
// RangeMatches*Point
|
||||||
//
|
//
|
||||||
// Compares the range beginning or ending point, and returns true if it
|
// Compares the range beginning or ending point, and returns true if it
|
||||||
|
@ -4142,53 +4047,23 @@ RangeMatchesEndPoint(nsIRange* aRange, nsINode* aNode, PRInt32 aOffset)
|
||||||
return aRange->GetEndParent() == aNode && aRange->EndOffset() == aOffset;
|
return aRange->GetEndParent() == aNode && aRange->EndOffset() == aOffset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// nsTypedSelection::EqualsRangeAtPoint
|
||||||
// nsTypedSelection::FindRangeGivenPoint
|
|
||||||
//
|
//
|
||||||
// Searches for the range matching the exact given range points. We search
|
// Utility method for checking equivalence of two ranges.
|
||||||
// in the array of beginnings, and start from the given index. This index
|
|
||||||
// should be the result of FindInsertionPoint, which will return any index
|
|
||||||
// within a range of identical ones.
|
|
||||||
//
|
|
||||||
// Therefore, this function searches backwards and forwards from that point
|
|
||||||
// of all matching beginning points, and then compares the ending points to
|
|
||||||
// find a match. Returns the index of the match if a match was found, -1 if
|
|
||||||
// not.
|
|
||||||
|
|
||||||
PRInt32
|
PRBool
|
||||||
nsTypedSelection::FindRangeGivenPoint(
|
nsTypedSelection::EqualsRangeAtPoint(
|
||||||
nsINode* aBeginNode, PRInt32 aBeginOffset,
|
nsINode* aBeginNode, PRInt32 aBeginOffset,
|
||||||
nsINode* aEndNode, PRInt32 aEndOffset,
|
nsINode* aEndNode, PRInt32 aEndOffset,
|
||||||
PRInt32 aStartSearchingHere)
|
PRInt32 aRangeIndex)
|
||||||
{
|
{
|
||||||
PRInt32 i;
|
if (aRangeIndex >=0 && aRangeIndex < mRanges.Length()) {
|
||||||
NS_ASSERTION(aStartSearchingHere >= 0 && aStartSearchingHere <= (PRInt32)mRanges.Length(),
|
nsIRange* range = mRanges[aRangeIndex].mRange;
|
||||||
"Input searching seed is not in range.");
|
if (RangeMatchesBeginPoint(range, aBeginNode, aBeginOffset)
|
||||||
|
&& RangeMatchesEndPoint(range, aEndNode, aEndOffset))
|
||||||
// search backwards for a begin match
|
return PR_TRUE;
|
||||||
for (i = aStartSearchingHere; i >= 0 && i < (PRInt32)mRanges.Length(); i --) {
|
|
||||||
if (RangeMatchesBeginPoint(mRanges[i].mRange, aBeginNode, aBeginOffset)) {
|
|
||||||
if (RangeMatchesEndPoint(mRanges[i].mRange, aEndNode, aEndOffset))
|
|
||||||
return i;
|
|
||||||
} else {
|
|
||||||
// done with matches going backwards
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
return PR_FALSE;
|
||||||
|
|
||||||
// search forwards for a begin match
|
|
||||||
for (i = aStartSearchingHere + 1; i < (PRInt32)mRanges.Length(); i ++) {
|
|
||||||
if (RangeMatchesBeginPoint(mRanges[i].mRange, aBeginNode, aBeginOffset)) {
|
|
||||||
if (RangeMatchesEndPoint(mRanges[i].mRange, aEndNode, aEndOffset))
|
|
||||||
return i;
|
|
||||||
} else {
|
|
||||||
// done with matches going forwards
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// match not found
|
|
||||||
return -1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//utility method to get the primary frame of node or use the offset to get frame of child node
|
//utility method to get the primary frame of node or use the offset to get frame of child node
|
||||||
|
@ -5346,7 +5221,6 @@ nsTypedSelection::GetIsCollapsed(PRBool* aIsCollapsed)
|
||||||
NS_IMETHODIMP
|
NS_IMETHODIMP
|
||||||
nsTypedSelection::GetRangeCount(PRInt32* aRangeCount)
|
nsTypedSelection::GetRangeCount(PRInt32* aRangeCount)
|
||||||
{
|
{
|
||||||
NS_ASSERTION(ValidateRanges(), "Ranges out of sync");
|
|
||||||
*aRangeCount = (PRInt32)mRanges.Length();
|
*aRangeCount = (PRInt32)mRanges.Length();
|
||||||
|
|
||||||
return NS_OK;
|
return NS_OK;
|
||||||
|
@ -5355,8 +5229,6 @@ nsTypedSelection::GetRangeCount(PRInt32* aRangeCount)
|
||||||
NS_IMETHODIMP
|
NS_IMETHODIMP
|
||||||
nsTypedSelection::GetRangeAt(PRInt32 aIndex, nsIDOMRange** aReturn)
|
nsTypedSelection::GetRangeAt(PRInt32 aIndex, nsIDOMRange** aReturn)
|
||||||
{
|
{
|
||||||
NS_ASSERTION(ValidateRanges(), "Ranges out of sync");
|
|
||||||
|
|
||||||
*aReturn = mRanges.SafeElementAt(aIndex, sEmptyData).mRange;
|
*aReturn = mRanges.SafeElementAt(aIndex, sEmptyData).mRange;
|
||||||
if (!*aReturn) {
|
if (!*aReturn) {
|
||||||
return NS_ERROR_INVALID_ARG;
|
return NS_ERROR_INVALID_ARG;
|
||||||
|
@ -5370,7 +5242,6 @@ nsTypedSelection::GetRangeAt(PRInt32 aIndex, nsIDOMRange** aReturn)
|
||||||
nsIRange*
|
nsIRange*
|
||||||
nsTypedSelection::GetRangeAt(PRInt32 aIndex)
|
nsTypedSelection::GetRangeAt(PRInt32 aIndex)
|
||||||
{
|
{
|
||||||
NS_ASSERTION(ValidateRanges(), "Ranges out of sync");
|
|
||||||
return mRanges.SafeElementAt(aIndex, sEmptyData).mRange;
|
return mRanges.SafeElementAt(aIndex, sEmptyData).mRange;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5725,7 +5596,6 @@ nsTypedSelection::ContainsNode(nsIDOMNode* aNode, PRBool aAllowPartial,
|
||||||
nsresult rv;
|
nsresult rv;
|
||||||
if (!aYes)
|
if (!aYes)
|
||||||
return NS_ERROR_NULL_POINTER;
|
return NS_ERROR_NULL_POINTER;
|
||||||
NS_ASSERTION(ValidateRanges(), "Ranges out of sync");
|
|
||||||
*aYes = PR_FALSE;
|
*aYes = PR_FALSE;
|
||||||
|
|
||||||
nsCOMPtr<nsINode> node = do_QueryInterface(aNode);
|
nsCOMPtr<nsINode> node = do_QueryInterface(aNode);
|
||||||
|
|
|
@ -56,6 +56,7 @@ _TEST_FILES = \
|
||||||
test_bug288789.html \
|
test_bug288789.html \
|
||||||
test_bug323656.html \
|
test_bug323656.html \
|
||||||
test_bug344830.html \
|
test_bug344830.html \
|
||||||
|
test_bug348681.html \
|
||||||
test_bug382429.html \
|
test_bug382429.html \
|
||||||
test_bug384527.html \
|
test_bug384527.html \
|
||||||
test_bug385751.html \
|
test_bug385751.html \
|
||||||
|
|
|
@ -0,0 +1,321 @@
|
||||||
|
<!DOCTYPE HTML>
|
||||||
|
<!-- ***** BEGIN LICENSE BLOCK *****
|
||||||
|
- Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||||
|
-
|
||||||
|
- The contents of this file are subject to the Mozilla Public License Version
|
||||||
|
- 1.1 (the "License"); you may not use this file except in compliance with
|
||||||
|
- the License. You may obtain a copy of the License at
|
||||||
|
- http://www.mozilla.org/MPL/
|
||||||
|
-
|
||||||
|
- Software distributed under the License is distributed on an "AS IS" basis,
|
||||||
|
- WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||||
|
- for the specific language governing rights and limitations under the
|
||||||
|
- License.
|
||||||
|
-
|
||||||
|
- The Original Code is Selection Test code
|
||||||
|
-
|
||||||
|
- The Initial Developer of the Original Code is
|
||||||
|
- Graeme McCutcheon <graememcc_firefox@graeme-online.co.uk>.
|
||||||
|
- Portions created by the Initial Developer are Copyright (C) 2009
|
||||||
|
- the Initial Developer. All Rights Reserved.
|
||||||
|
-
|
||||||
|
- Contributor(s):
|
||||||
|
-
|
||||||
|
-
|
||||||
|
- Alternatively, the contents of this file may be used under the terms of
|
||||||
|
- either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||||
|
- the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||||
|
- in which case the provisions of the GPL or the LGPL are applicable instead
|
||||||
|
- of those above. If you wish to allow use of your version of this file only
|
||||||
|
- under the terms of either the GPL or the LGPL, and not to allow others to
|
||||||
|
- use your version of this file under the terms of the MPL, indicate your
|
||||||
|
- decision by deleting the provisions above and replace them with the notice
|
||||||
|
- and other provisions required by the GPL or the LGPL. If you do not delete
|
||||||
|
- the provisions above, a recipient may use your version of this file under
|
||||||
|
- the terms of any one of the MPL, the GPL or the LGPL.
|
||||||
|
-
|
||||||
|
- ***** END LICENSE BLOCK ***** -->
|
||||||
|
<html>
|
||||||
|
<!--
|
||||||
|
https://bugzilla.mozilla.org/show_bug.cgi?id=348681
|
||||||
|
-->
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<title>Test for Bug 348681</head>
|
||||||
|
<script type="application/javascript" src="/MochiKit/MochiKit.js"></script>
|
||||||
|
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||||
|
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
|
||||||
|
<script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body onload="doTest();">
|
||||||
|
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=348681">Mozilla Bug 348681</a>
|
||||||
|
<p id="display"></p>
|
||||||
|
<div id="content" style="display: none">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<pre id="test">
|
||||||
|
<script type="application/javascript">
|
||||||
|
|
||||||
|
/** Test for Bug 348681 **/
|
||||||
|
|
||||||
|
SimpleTest.waitForExplicitFinish();
|
||||||
|
|
||||||
|
var rangeChecker = {
|
||||||
|
ranges: [],
|
||||||
|
|
||||||
|
reset: function() {
|
||||||
|
this.ranges = [];
|
||||||
|
},
|
||||||
|
|
||||||
|
check: function(testID, selection) {
|
||||||
|
is(selection.rangeCount, this.ranges.length,
|
||||||
|
"Test "+testID+": correct number of ranges in selection");
|
||||||
|
var rangesMatch = true;
|
||||||
|
var foundMsg = "Found ";
|
||||||
|
var expectedMsg = "Expected ";
|
||||||
|
var maxIndex = Math.max(selection.rangeCount, this.ranges.length);
|
||||||
|
for (var x = 0; x < maxIndex; x++) {
|
||||||
|
var expect = null;
|
||||||
|
if (x < this.ranges.length)
|
||||||
|
expect = this.ranges[x];
|
||||||
|
|
||||||
|
var found = null;
|
||||||
|
if (x < selection.rangeCount)
|
||||||
|
found = selection.getRangeAt(x);
|
||||||
|
|
||||||
|
if (found) {
|
||||||
|
foundMsg +="["+found.startOffset+","+found.endOffset+"] ";
|
||||||
|
} else {
|
||||||
|
foundMsg +="[] ";
|
||||||
|
}
|
||||||
|
if (expect) {
|
||||||
|
expectedMsg +="["+expect.start+","+expect.end+"] ";
|
||||||
|
} else {
|
||||||
|
expectedMsg+="[] ";
|
||||||
|
}
|
||||||
|
if (found && expect) {
|
||||||
|
if (found.startContainer != expect.node ||
|
||||||
|
found.endContainer != expect.node ||
|
||||||
|
found.startOffset != expect.start ||
|
||||||
|
found.endOffset != expect.end)
|
||||||
|
rangesMatch = false;
|
||||||
|
} else {
|
||||||
|
rangesMatch = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var okMsg = "Test "+testID+": correct ranges in selection.";
|
||||||
|
okMsg = okMsg + foundMsg + expectedMsg;
|
||||||
|
ok(rangesMatch, okMsg);
|
||||||
|
},
|
||||||
|
|
||||||
|
addExpected: function(node, start, end) {
|
||||||
|
var expected = {};
|
||||||
|
expected.node = node;
|
||||||
|
expected.start = start;
|
||||||
|
expected.end = end;
|
||||||
|
this.ranges[this.ranges.length] = expected;
|
||||||
|
this.ranges.sort(function(a,b) {return a.start - b.start;});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function doTest() {
|
||||||
|
var testNode = document.getElementById("testparagraph").firstChild;
|
||||||
|
|
||||||
|
var selection = window.getSelection();
|
||||||
|
selection.removeAllRanges();
|
||||||
|
ok(selection.rangeCount == 0, "Test 0 - No selections so far");
|
||||||
|
|
||||||
|
// Test 1. Add a single range, to ensure we've not broken anything
|
||||||
|
var range1 = document.createRange();
|
||||||
|
range1.setStart(testNode, 0);
|
||||||
|
range1.setEnd(testNode, 5);
|
||||||
|
selection.addRange(range1);
|
||||||
|
rangeChecker.addExpected(testNode, 0, 5);
|
||||||
|
rangeChecker.check(1, selection);
|
||||||
|
|
||||||
|
// Test 2. Add a non-overlapping range, to ensure it gets added too
|
||||||
|
var range2 = document.createRange();
|
||||||
|
range2.setStart(testNode, 8);
|
||||||
|
range2.setEnd(testNode, 10);
|
||||||
|
selection.addRange(range2);
|
||||||
|
rangeChecker.addExpected(testNode, 8, 10);
|
||||||
|
rangeChecker.check(2, selection);
|
||||||
|
|
||||||
|
// Test 3. Add the same range again. This should silently succeed
|
||||||
|
selection.addRange(range2);
|
||||||
|
rangeChecker.check(3, selection);
|
||||||
|
|
||||||
|
// Test 4. Add a range that is left-adjacent to an existing range
|
||||||
|
var range3 = document.createRange();
|
||||||
|
range3.setStart(testNode, 6);
|
||||||
|
range3.setEnd(testNode, 8);
|
||||||
|
selection.addRange(range3);
|
||||||
|
rangeChecker.addExpected(testNode, 6, 8);
|
||||||
|
rangeChecker.check(4, selection);
|
||||||
|
|
||||||
|
// Test 5. Add a range that is right-adjacent to an existing range
|
||||||
|
var range4 = document.createRange();
|
||||||
|
range4.setStart(testNode, 10);
|
||||||
|
range4.setEnd(testNode, 12);
|
||||||
|
selection.addRange(range4);
|
||||||
|
rangeChecker.addExpected(testNode, 10, 12);
|
||||||
|
rangeChecker.check(5, selection);
|
||||||
|
|
||||||
|
// Test 6. Add a range, add a second range that overlaps it
|
||||||
|
var range5 = document.createRange();
|
||||||
|
range5.setStart(testNode, 20);
|
||||||
|
range5.setEnd(testNode, 25);
|
||||||
|
selection.addRange(range5);
|
||||||
|
var range6 = document.createRange();
|
||||||
|
range6.setStart(testNode, 23);
|
||||||
|
range6.setEnd(testNode, 26);
|
||||||
|
selection.addRange(range6);
|
||||||
|
rangeChecker.addExpected(testNode, 20, 23);
|
||||||
|
rangeChecker.addExpected(testNode, 23, 26);
|
||||||
|
rangeChecker.check(6, selection);
|
||||||
|
|
||||||
|
// Test 7. Add a range, and this time a left-hand overlap
|
||||||
|
var range7 = document.createRange();
|
||||||
|
range7.setStart(testNode, 30);
|
||||||
|
range7.setEnd(testNode, 35);
|
||||||
|
selection.addRange(range7);
|
||||||
|
var range8 = document.createRange();
|
||||||
|
range8.setStart(testNode, 28);
|
||||||
|
range8.setEnd(testNode, 31);
|
||||||
|
selection.addRange(range8);
|
||||||
|
rangeChecker.addExpected(testNode, 31, 35);
|
||||||
|
rangeChecker.addExpected(testNode, 28, 31);
|
||||||
|
rangeChecker.check(7, selection);
|
||||||
|
|
||||||
|
// Test 8. Add a range, and add a second range wholly contained
|
||||||
|
// within it
|
||||||
|
var range9 = document.createRange();
|
||||||
|
range9.setStart(testNode, 40);
|
||||||
|
range9.setEnd(testNode, 50);
|
||||||
|
selection.addRange(range9);
|
||||||
|
var range10 = document.createRange();
|
||||||
|
range10.setStart(testNode, 43);
|
||||||
|
range10.setEnd(testNode, 47);
|
||||||
|
selection.addRange(range10);
|
||||||
|
rangeChecker.addExpected(testNode, 40, 43);
|
||||||
|
rangeChecker.addExpected(testNode, 43, 47);
|
||||||
|
rangeChecker.addExpected(testNode, 47, 50);
|
||||||
|
rangeChecker.check(8, selection);
|
||||||
|
|
||||||
|
// Test 9. Add a range that will wholly contain some existing ranges
|
||||||
|
rangeChecker.reset();
|
||||||
|
selection.removeAllRanges();
|
||||||
|
var range11 = document.createRange();
|
||||||
|
range11.setStart(testNode, 5);
|
||||||
|
range11.setEnd(testNode, 7);
|
||||||
|
selection.addRange(range11);
|
||||||
|
var range12 = document.createRange();
|
||||||
|
range12.setStart(testNode, 9);
|
||||||
|
range12.setEnd(testNode, 11);
|
||||||
|
selection.addRange(range12);
|
||||||
|
var range13 = document.createRange();
|
||||||
|
range13.setStart(testNode, 4);
|
||||||
|
range13.setEnd(testNode, 12);
|
||||||
|
selection.addRange(range13);
|
||||||
|
rangeChecker.addExpected(testNode, 4, 12);
|
||||||
|
rangeChecker.check(9, selection);
|
||||||
|
|
||||||
|
// Test 10. This time with the last range being a partial overlap
|
||||||
|
selection.removeAllRanges();
|
||||||
|
selection.addRange(range11);
|
||||||
|
selection.addRange(range12);
|
||||||
|
var range14 = document.createRange();
|
||||||
|
range14.setStart(testNode, 11);
|
||||||
|
range14.setEnd(testNode, 14);
|
||||||
|
selection.addRange(range14);
|
||||||
|
selection.addRange(range13);
|
||||||
|
rangeChecker.addExpected(testNode, 12, 14);
|
||||||
|
rangeChecker.check(10, selection);
|
||||||
|
|
||||||
|
// Test 11. Check we can add a collapsed range without problem
|
||||||
|
selection.removeAllRanges();
|
||||||
|
rangeChecker.reset();
|
||||||
|
range1.collapse(true);
|
||||||
|
selection.addRange(range1);
|
||||||
|
rangeChecker.addExpected(testNode, 0, 0);
|
||||||
|
rangeChecker.check(11, selection);
|
||||||
|
|
||||||
|
// Test 12. Check GetRangesForInterval returns correct results
|
||||||
|
// part 1 - non-adjacency
|
||||||
|
selection.removeAllRanges();
|
||||||
|
selection.addRange(range2);
|
||||||
|
selection.addRange(range3);
|
||||||
|
selection.addRange(range4);
|
||||||
|
var sel2 = selection.QueryInterface(Components.interfaces.nsISelection2);
|
||||||
|
ok(sel2, "Test 12 - QIed to instance of nsISelection2 interface");
|
||||||
|
var numResults = {};
|
||||||
|
var results = sel2.GetRangesForInterval(testNode, 8, testNode, 10,
|
||||||
|
false, numResults);
|
||||||
|
is(numResults.value, 1, "Test 12 - Correct number of ranges returned");
|
||||||
|
var result = results[0];
|
||||||
|
var correctNode = result.startContainer == testNode &&
|
||||||
|
result.endContainer == testNode &&
|
||||||
|
result.startOffset == 8 && result.endOffset == 10;
|
||||||
|
ok(correctNode, "Test 12 - Correct range returned");
|
||||||
|
|
||||||
|
// Test 13. Check GetRangesForInterval returns correct results
|
||||||
|
// part 2 - adjacent ranges allowed
|
||||||
|
numResults = {};
|
||||||
|
results = sel2.GetRangesForInterval(testNode, 8, testNode, 10,
|
||||||
|
true, numResults);
|
||||||
|
is(numResults.value, 3, "Test 13 - Correct number of ranges returned");
|
||||||
|
correctNode = true;
|
||||||
|
for (var x = 0; x < numResults.value; x++) {
|
||||||
|
var result = results[x];
|
||||||
|
var correct = result.startContainer == testNode &&
|
||||||
|
result.endContainer == testNode &&
|
||||||
|
result.startOffset == (2*x+6) &&
|
||||||
|
result.endOffset == (2*x+8);
|
||||||
|
correctNode = correctNode && correct;
|
||||||
|
}
|
||||||
|
ok(correctNode, "Test 13 - Correct ranges returned");
|
||||||
|
|
||||||
|
// Test 14. Check GetRangesForInterval returns correct results
|
||||||
|
// part 3 - no ranges in interval
|
||||||
|
numResults = {};
|
||||||
|
results = sel2.GetRangesForInterval(testNode, 14, testNode, 16,
|
||||||
|
true, numResults);
|
||||||
|
is(numResults.value, 0, "Test 14 - Correct number of ranges returned");
|
||||||
|
|
||||||
|
// Test 15. Check that when adding a range where after overlap
|
||||||
|
// adjustment an exisiting range would collapse that the collapsed range
|
||||||
|
// is removed - part 1 (LHS)
|
||||||
|
selection.removeAllRanges();
|
||||||
|
rangeChecker.reset();
|
||||||
|
selection.addRange(range2);
|
||||||
|
var range15 = document.createRange();
|
||||||
|
range15.setStart(testNode, 6);
|
||||||
|
range15.setEnd(testNode, 10);
|
||||||
|
selection.addRange(range15);
|
||||||
|
rangeChecker.addExpected(testNode, 6, 10);
|
||||||
|
rangeChecker.check(15, selection);
|
||||||
|
|
||||||
|
// Test 16. Check that when adding a range where after overlap
|
||||||
|
// adjustment an exisiting range would collapse that the collapsed range
|
||||||
|
// is removed - part 2 (RHS)
|
||||||
|
selection.removeAllRanges();
|
||||||
|
rangeChecker.reset();
|
||||||
|
selection.addRange(range2);
|
||||||
|
var range16 = document.createRange();
|
||||||
|
range16.setStart(testNode, 8);
|
||||||
|
range16.setEnd(testNode, 12);
|
||||||
|
selection.addRange(range16);
|
||||||
|
rangeChecker.addExpected(testNode, 8, 12);
|
||||||
|
rangeChecker.check(16, selection);
|
||||||
|
|
||||||
|
SimpleTest.finish();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p id="testparagraph">
|
||||||
|
This will be the first child of the p node above. The intention is that it is a suitably long text node to which we can add multiple ranges in order to test the various aspects of the bug.
|
||||||
|
</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
Загрузка…
Ссылка в новой задаче