1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "SelectionState.h"
8 #include "mozilla/Assertions.h" // for MOZ_ASSERT, etc.
9 #include "mozilla/EditorUtils.h" // for EditorUtils
10 #include "mozilla/dom/RangeBinding.h"
11 #include "mozilla/dom/Selection.h" // for Selection
12 #include "nsAString.h" // for nsAString::Length
13 #include "nsCycleCollectionParticipant.h"
14 #include "nsDebug.h" // for NS_WARNING, etc.
15 #include "nsError.h" // for NS_OK, etc.
16 #include "nsIContent.h" // for nsIContent
17 #include "nsISupportsImpl.h" // for nsRange::Release
18 #include "nsRange.h" // for nsRange
24 /******************************************************************************
25 * mozilla::SelectionState
27 * Class for recording selection info. Stores selection as collection of
28 * { {startnode, startoffset} , {endnode, endoffset} } tuples. Can't store
29 * ranges since dom gravity will possibly change the ranges.
30 ******************************************************************************/
32 template nsresult
RangeUpdater::SelAdjCreateNode(const EditorDOMPoint
& aPoint
);
33 template nsresult
RangeUpdater::SelAdjCreateNode(
34 const EditorRawDOMPoint
& aPoint
);
35 template nsresult
RangeUpdater::SelAdjInsertNode(const EditorDOMPoint
& aPoint
);
36 template nsresult
RangeUpdater::SelAdjInsertNode(
37 const EditorRawDOMPoint
& aPoint
);
39 SelectionState::SelectionState() : mDirection(eDirNext
) {}
41 void SelectionState::SaveSelection(Selection
& aSelection
) {
42 int32_t arrayCount
= mArray
.Length();
43 int32_t rangeCount
= aSelection
.RangeCount();
45 // if we need more items in the array, new them
46 if (arrayCount
< rangeCount
) {
47 for (int32_t i
= arrayCount
; i
< rangeCount
; i
++) {
48 mArray
.AppendElement();
49 mArray
[i
] = new RangeItem();
51 } else if (arrayCount
> rangeCount
) {
52 // else if we have too many, delete them
53 for (int32_t i
= arrayCount
- 1; i
>= rangeCount
; i
--) {
54 mArray
.RemoveElementAt(i
);
58 // now store the selection ranges
59 for (int32_t i
= 0; i
< rangeCount
; i
++) {
60 const nsRange
* range
= aSelection
.GetRangeAt(i
);
61 if (NS_WARN_IF(!range
)) {
64 mArray
[i
]->StoreRange(*range
);
67 mDirection
= aSelection
.GetDirection();
70 nsresult
SelectionState::RestoreSelection(Selection
& aSelection
) {
71 // clear out selection
72 IgnoredErrorResult ignoredError
;
73 aSelection
.RemoveAllRanges(ignoredError
);
74 NS_WARNING_ASSERTION(!ignoredError
.Failed(),
75 "Selection::RemoveAllRanges() failed, but ignored");
77 aSelection
.SetDirection(mDirection
);
80 const CopyableAutoTArray
<RefPtr
<RangeItem
>, 10> rangeItems(mArray
);
81 for (const RefPtr
<RangeItem
>& rangeItem
: rangeItems
) {
82 RefPtr
<nsRange
> range
= rangeItem
->GetRange();
84 NS_WARNING("RangeItem::GetRange() failed");
85 return NS_ERROR_FAILURE
;
87 aSelection
.AddRangeAndSelectFramesAndNotifyListeners(*range
, error
);
90 "Selection::AddRangeAndSelectFramesAndNotifyListeners() failed");
91 return error
.StealNSResult();
97 bool SelectionState::IsCollapsed() const {
98 if (mArray
.Length() != 1) {
101 RefPtr
<nsRange
> range
= mArray
[0]->GetRange();
103 NS_WARNING("RangeItem::GetRange() failed");
106 return range
->Collapsed();
109 bool SelectionState::Equals(SelectionState
& aOther
) const {
110 if (mArray
.Length() != aOther
.mArray
.Length()) {
113 if (mArray
.IsEmpty()) {
114 return false; // XXX Why?
116 if (mDirection
!= aOther
.mDirection
) {
120 // XXX Creating nsRanges are really expensive. Why cannot we just check
121 // the container and offsets??
122 IgnoredErrorResult ignoredError
;
123 for (size_t i
= 0; i
< mArray
.Length(); i
++) {
124 RefPtr
<nsRange
> range
= mArray
[i
]->GetRange();
126 NS_WARNING("Failed to create a range from the range item");
129 RefPtr
<nsRange
> otherRange
= aOther
.mArray
[i
]->GetRange();
131 NS_WARNING("Failed to create a range from the other's range item");
135 int16_t compResult
= range
->CompareBoundaryPoints(
136 Range_Binding::START_TO_START
, *otherRange
, ignoredError
);
137 if (ignoredError
.Failed()) {
139 "nsRange::CompareBoundaryPoints(Range_Binding::START_TO_START) "
146 compResult
= range
->CompareBoundaryPoints(Range_Binding::END_TO_END
,
147 *otherRange
, ignoredError
);
148 if (ignoredError
.Failed()) {
150 "nsRange::CompareBoundaryPoints(Range_Binding::END_TO_END) failed");
157 // if we got here, they are equal
161 void SelectionState::Clear() {
162 // free any items in the array
164 mDirection
= eDirNext
;
167 bool SelectionState::IsEmpty() const { return mArray
.IsEmpty(); }
169 /******************************************************************************
170 * mozilla::RangeUpdater
172 * Class for updating nsRanges in response to editor actions.
173 ******************************************************************************/
175 RangeUpdater::RangeUpdater() : mLocked(false) {}
177 void RangeUpdater::RegisterRangeItem(RangeItem
& aRangeItem
) {
178 if (mArray
.Contains(&aRangeItem
)) {
179 NS_ERROR("tried to register an already registered range");
180 return; // don't register it again. It would get doubly adjusted.
182 mArray
.AppendElement(&aRangeItem
);
185 void RangeUpdater::DropRangeItem(RangeItem
& aRangeItem
) {
186 NS_WARNING_ASSERTION(
187 mArray
.Contains(&aRangeItem
),
188 "aRangeItem is not in the range, but tried to removed from it");
189 mArray
.RemoveElement(&aRangeItem
);
192 void RangeUpdater::RegisterSelectionState(SelectionState
& aSelectionState
) {
193 for (RefPtr
<RangeItem
>& rangeItem
: aSelectionState
.mArray
) {
194 if (NS_WARN_IF(!rangeItem
)) {
197 RegisterRangeItem(*rangeItem
);
201 void RangeUpdater::DropSelectionState(SelectionState
& aSelectionState
) {
202 for (RefPtr
<RangeItem
>& rangeItem
: aSelectionState
.mArray
) {
203 if (NS_WARN_IF(!rangeItem
)) {
206 DropRangeItem(*rangeItem
);
212 template <typename PT
, typename CT
>
213 nsresult
RangeUpdater::SelAdjCreateNode(
214 const EditorDOMPointBase
<PT
, CT
>& aPoint
) {
216 // lock set by Will/DidReplaceParent, etc...
219 if (mArray
.IsEmpty()) {
223 if (NS_WARN_IF(!aPoint
.IsSetAndValid())) {
224 return NS_ERROR_INVALID_ARG
;
227 for (RefPtr
<RangeItem
>& rangeItem
: mArray
) {
228 if (NS_WARN_IF(!rangeItem
)) {
229 return NS_ERROR_FAILURE
;
231 if (rangeItem
->mStartContainer
== aPoint
.GetContainer() &&
232 rangeItem
->mStartOffset
> static_cast<int32_t>(aPoint
.Offset())) {
233 rangeItem
->mStartOffset
++;
235 if (rangeItem
->mEndContainer
== aPoint
.GetContainer() &&
236 rangeItem
->mEndOffset
> static_cast<int32_t>(aPoint
.Offset())) {
237 rangeItem
->mEndOffset
++;
243 template <typename PT
, typename CT
>
244 nsresult
RangeUpdater::SelAdjInsertNode(
245 const EditorDOMPointBase
<PT
, CT
>& aPoint
) {
246 nsresult rv
= SelAdjCreateNode(aPoint
);
247 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
248 "RangeUpdater::SelAdjCreateNode() failed");
252 void RangeUpdater::SelAdjDeleteNode(nsINode
& aNodeToDelete
) {
254 // lock set by Will/DidReplaceParent, etc...
258 if (mArray
.IsEmpty()) {
262 EditorRawDOMPoint
atNodeToDelete(&aNodeToDelete
);
263 NS_ASSERTION(atNodeToDelete
.IsSetAndValid(),
264 "aNodeToDelete must be an orphan node or this is called "
266 // check for range endpoints that are after aNodeToDelete and in the same
268 for (RefPtr
<RangeItem
>& rangeItem
: mArray
) {
269 MOZ_ASSERT(rangeItem
);
271 if (rangeItem
->mStartContainer
== atNodeToDelete
.GetContainer() &&
272 rangeItem
->mStartOffset
>
273 static_cast<int32_t>(atNodeToDelete
.Offset())) {
274 rangeItem
->mStartOffset
--;
276 if (rangeItem
->mEndContainer
== atNodeToDelete
.GetContainer() &&
277 rangeItem
->mEndOffset
> static_cast<int32_t>(atNodeToDelete
.Offset())) {
278 rangeItem
->mEndOffset
--;
281 // check for range endpoints that are in aNodeToDelete
282 if (rangeItem
->mStartContainer
== &aNodeToDelete
) {
283 rangeItem
->mStartContainer
= atNodeToDelete
.GetContainer();
284 rangeItem
->mStartOffset
= atNodeToDelete
.Offset();
286 if (rangeItem
->mEndContainer
== &aNodeToDelete
) {
287 rangeItem
->mEndContainer
= atNodeToDelete
.GetContainer();
288 rangeItem
->mEndOffset
= atNodeToDelete
.Offset();
291 // check for range endpoints that are in descendants of aNodeToDelete
292 bool updateEndBoundaryToo
= false;
293 if (EditorUtils::IsDescendantOf(*rangeItem
->mStartContainer
,
295 updateEndBoundaryToo
=
296 rangeItem
->mStartContainer
== rangeItem
->mEndContainer
;
297 rangeItem
->mStartContainer
= atNodeToDelete
.GetContainer();
298 rangeItem
->mStartOffset
= atNodeToDelete
.Offset();
301 // avoid having to call IsDescendantOf() for common case of range startnode
303 if (updateEndBoundaryToo
||
304 EditorUtils::IsDescendantOf(*rangeItem
->mEndContainer
, aNodeToDelete
)) {
305 rangeItem
->mEndContainer
= atNodeToDelete
.GetContainer();
306 rangeItem
->mEndOffset
= atNodeToDelete
.Offset();
311 nsresult
RangeUpdater::SelAdjSplitNode(nsIContent
& aRightNode
,
312 nsIContent
& aNewLeftNode
) {
314 // lock set by Will/DidReplaceParent, etc...
318 if (mArray
.IsEmpty()) {
322 EditorRawDOMPoint
atLeftNode(&aNewLeftNode
);
323 nsresult rv
= SelAdjInsertNode(atLeftNode
);
325 NS_WARNING("RangeUpdater::SelAdjInsertNode() failed");
329 // If point in the ranges is in left node, change its container to the left
330 // node. If point in the ranges is in right node, subtract numbers of
331 // children moved to left node from the offset.
332 int32_t lengthOfLeftNode
= aNewLeftNode
.Length();
333 for (RefPtr
<RangeItem
>& rangeItem
: mArray
) {
334 if (NS_WARN_IF(!rangeItem
)) {
335 return NS_ERROR_FAILURE
;
338 if (rangeItem
->mStartContainer
== &aRightNode
) {
339 if (rangeItem
->mStartOffset
> lengthOfLeftNode
) {
340 rangeItem
->mStartOffset
-= lengthOfLeftNode
;
342 rangeItem
->mStartContainer
= &aNewLeftNode
;
345 if (rangeItem
->mEndContainer
== &aRightNode
) {
346 if (rangeItem
->mEndOffset
> lengthOfLeftNode
) {
347 rangeItem
->mEndOffset
-= lengthOfLeftNode
;
349 rangeItem
->mEndContainer
= &aNewLeftNode
;
356 nsresult
RangeUpdater::SelAdjJoinNodes(nsINode
& aLeftNode
, nsINode
& aRightNode
,
357 nsINode
& aParent
, int32_t aOffset
,
358 int32_t aOldLeftNodeLength
) {
360 // lock set by Will/DidReplaceParent, etc...
364 if (mArray
.IsEmpty()) {
368 for (RefPtr
<RangeItem
>& rangeItem
: mArray
) {
369 if (NS_WARN_IF(!rangeItem
)) {
370 return NS_ERROR_FAILURE
;
373 if (rangeItem
->mStartContainer
== &aParent
) {
374 // adjust start point in aParent
375 if (rangeItem
->mStartOffset
> aOffset
) {
376 rangeItem
->mStartOffset
--;
377 } else if (rangeItem
->mStartOffset
== aOffset
) {
378 // join keeps right hand node
379 rangeItem
->mStartContainer
= &aRightNode
;
380 rangeItem
->mStartOffset
= aOldLeftNodeLength
;
382 } else if (rangeItem
->mStartContainer
== &aRightNode
) {
383 // adjust start point in aRightNode
384 rangeItem
->mStartOffset
+= aOldLeftNodeLength
;
385 } else if (rangeItem
->mStartContainer
== &aLeftNode
) {
386 // adjust start point in aLeftNode
387 rangeItem
->mStartContainer
= &aRightNode
;
390 if (rangeItem
->mEndContainer
== &aParent
) {
391 // adjust end point in aParent
392 if (rangeItem
->mEndOffset
> aOffset
) {
393 rangeItem
->mEndOffset
--;
394 } else if (rangeItem
->mEndOffset
== aOffset
) {
395 // join keeps right hand node
396 rangeItem
->mEndContainer
= &aRightNode
;
397 rangeItem
->mEndOffset
= aOldLeftNodeLength
;
399 } else if (rangeItem
->mEndContainer
== &aRightNode
) {
400 // adjust end point in aRightNode
401 rangeItem
->mEndOffset
+= aOldLeftNodeLength
;
402 } else if (rangeItem
->mEndContainer
== &aLeftNode
) {
403 // adjust end point in aLeftNode
404 rangeItem
->mEndContainer
= &aRightNode
;
411 void RangeUpdater::SelAdjReplaceText(const Text
& aTextNode
, int32_t aOffset
,
412 int32_t aReplacedLength
,
413 int32_t aInsertedLength
) {
415 // lock set by Will/DidReplaceParent, etc...
419 // First, adjust selection for insertion because when offset is in the
420 // replaced range, it's adjusted to aOffset and never modified by the
421 // insertion if we adjust selection for deletion first.
422 SelAdjInsertText(aTextNode
, aOffset
, aInsertedLength
);
424 // Then, adjust selection for deletion.
425 SelAdjDeleteText(aTextNode
, aOffset
, aReplacedLength
);
428 void RangeUpdater::SelAdjInsertText(const Text
& aTextNode
, int32_t aOffset
,
429 int32_t aInsertedLength
) {
431 // lock set by Will/DidReplaceParent, etc...
435 for (RefPtr
<RangeItem
>& rangeItem
: mArray
) {
436 MOZ_ASSERT(rangeItem
);
438 if (rangeItem
->mStartContainer
== &aTextNode
&&
439 rangeItem
->mStartOffset
> aOffset
) {
440 rangeItem
->mStartOffset
+= aInsertedLength
;
442 if (rangeItem
->mEndContainer
== &aTextNode
&&
443 rangeItem
->mEndOffset
> aOffset
) {
444 rangeItem
->mEndOffset
+= aInsertedLength
;
449 void RangeUpdater::SelAdjDeleteText(const Text
& aTextNode
, int32_t aOffset
,
450 int32_t aDeletedLength
) {
452 // lock set by Will/DidReplaceParent, etc...
456 for (RefPtr
<RangeItem
>& rangeItem
: mArray
) {
457 MOZ_ASSERT(rangeItem
);
459 if (rangeItem
->mStartContainer
== &aTextNode
&&
460 rangeItem
->mStartOffset
> aOffset
) {
461 rangeItem
->mStartOffset
-= aDeletedLength
;
462 if (rangeItem
->mStartOffset
< 0) {
463 rangeItem
->mStartOffset
= 0;
466 if (rangeItem
->mEndContainer
== &aTextNode
&&
467 rangeItem
->mEndOffset
> aOffset
) {
468 rangeItem
->mEndOffset
-= aDeletedLength
;
469 if (rangeItem
->mEndOffset
< 0) {
470 rangeItem
->mEndOffset
= 0;
476 void RangeUpdater::DidReplaceContainer(const Element
& aRemovedElement
,
477 Element
& aInsertedElement
) {
478 if (NS_WARN_IF(!mLocked
)) {
483 for (RefPtr
<RangeItem
>& rangeItem
: mArray
) {
484 if (NS_WARN_IF(!rangeItem
)) {
488 if (rangeItem
->mStartContainer
== &aRemovedElement
) {
489 rangeItem
->mStartContainer
= &aInsertedElement
;
491 if (rangeItem
->mEndContainer
== &aRemovedElement
) {
492 rangeItem
->mEndContainer
= &aInsertedElement
;
497 void RangeUpdater::DidRemoveContainer(const Element
& aRemovedElement
,
498 nsINode
& aRemovedElementContainerNode
,
499 uint32_t aOldOffsetOfRemovedElement
,
500 uint32_t aOldChildCountOfRemovedElement
) {
501 if (NS_WARN_IF(!mLocked
)) {
506 for (RefPtr
<RangeItem
>& rangeItem
: mArray
) {
507 if (NS_WARN_IF(!rangeItem
)) {
511 if (rangeItem
->mStartContainer
== &aRemovedElement
) {
512 rangeItem
->mStartContainer
= &aRemovedElementContainerNode
;
513 rangeItem
->mStartOffset
+= aOldOffsetOfRemovedElement
;
514 } else if (rangeItem
->mStartContainer
== &aRemovedElementContainerNode
&&
515 rangeItem
->mStartOffset
>
516 static_cast<int32_t>(aOldOffsetOfRemovedElement
)) {
517 rangeItem
->mStartOffset
+= aOldChildCountOfRemovedElement
- 1;
520 if (rangeItem
->mEndContainer
== &aRemovedElement
) {
521 rangeItem
->mEndContainer
= &aRemovedElementContainerNode
;
522 rangeItem
->mEndOffset
+= aOldOffsetOfRemovedElement
;
523 } else if (rangeItem
->mEndContainer
== &aRemovedElementContainerNode
&&
524 rangeItem
->mEndOffset
>
525 static_cast<int32_t>(aOldOffsetOfRemovedElement
)) {
526 rangeItem
->mEndOffset
+= aOldChildCountOfRemovedElement
- 1;
531 void RangeUpdater::DidMoveNode(const nsINode
& aOldParent
, int32_t aOldOffset
,
532 const nsINode
& aNewParent
, int32_t aNewOffset
) {
533 if (NS_WARN_IF(!mLocked
)) {
538 for (RefPtr
<RangeItem
>& rangeItem
: mArray
) {
539 if (NS_WARN_IF(!rangeItem
)) {
543 // like a delete in aOldParent
544 if (rangeItem
->mStartContainer
== &aOldParent
&&
545 rangeItem
->mStartOffset
> aOldOffset
) {
546 rangeItem
->mStartOffset
--;
548 if (rangeItem
->mEndContainer
== &aOldParent
&&
549 rangeItem
->mEndOffset
> aOldOffset
) {
550 rangeItem
->mEndOffset
--;
553 // and like an insert in aNewParent
554 if (rangeItem
->mStartContainer
== &aNewParent
&&
555 rangeItem
->mStartOffset
> aNewOffset
) {
556 rangeItem
->mStartOffset
++;
558 if (rangeItem
->mEndContainer
== &aNewParent
&&
559 rangeItem
->mEndOffset
> aNewOffset
) {
560 rangeItem
->mEndOffset
++;
565 /******************************************************************************
568 * Helper struct for SelectionState. This stores range endpoints.
569 ******************************************************************************/
571 NS_IMPL_CYCLE_COLLECTION(RangeItem
, mStartContainer
, mEndContainer
)
572 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(RangeItem
, AddRef
)
573 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(RangeItem
, Release
)
575 void RangeItem::StoreRange(const nsRange
& aRange
) {
576 mStartContainer
= aRange
.GetStartContainer();
577 mStartOffset
= aRange
.StartOffset();
578 mEndContainer
= aRange
.GetEndContainer();
579 mEndOffset
= aRange
.EndOffset();
582 already_AddRefed
<nsRange
> RangeItem::GetRange() {
583 RefPtr
<nsRange
> range
= nsRange::Create(
584 mStartContainer
, mStartOffset
, mEndContainer
, mEndOffset
, IgnoreErrors());
585 NS_WARNING_ASSERTION(range
, "nsRange::Create() failed");
586 return range
.forget();
589 } // namespace mozilla