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 "EditorUtils.h" // for EditorUtils
9 #include "HTMLEditHelpers.h" // for JoinNodesDirection, SplitNodeDirection
11 #include "mozilla/Assertions.h" // for MOZ_ASSERT, etc.
12 #include "mozilla/IntegerRange.h" // for IntegerRange
13 #include "mozilla/Likely.h" // For MOZ_LIKELY and MOZ_UNLIKELY
14 #include "mozilla/RangeUtils.h" // for RangeUtils
15 #include "mozilla/dom/RangeBinding.h"
16 #include "mozilla/dom/Selection.h" // for Selection
17 #include "nsAString.h" // for nsAString::Length
18 #include "nsCycleCollectionParticipant.h"
19 #include "nsDebug.h" // for NS_WARNING, etc.
20 #include "nsError.h" // for NS_OK, etc.
21 #include "nsIContent.h" // for nsIContent
22 #include "nsISupportsImpl.h" // for nsRange::Release
23 #include "nsRange.h" // for nsRange
29 /*****************************************************************************
31 *****************************************************************************/
33 nsINode
* RangeItem::GetRoot() const {
34 if (MOZ_UNLIKELY(!IsPositioned())) {
37 nsINode
* rootNode
= RangeUtils::ComputeRootNode(mStartContainer
);
38 if (mStartContainer
== mEndContainer
) {
41 return MOZ_LIKELY(rootNode
== RangeUtils::ComputeRootNode(mEndContainer
))
46 /******************************************************************************
47 * mozilla::SelectionState
49 * Class for recording selection info. Stores selection as collection of
50 * { {startnode, startoffset} , {endnode, endoffset} } tuples. Can't store
51 * ranges since dom gravity will possibly change the ranges.
52 ******************************************************************************/
54 template nsresult
RangeUpdater::SelAdjCreateNode(const EditorDOMPoint
& aPoint
);
55 template nsresult
RangeUpdater::SelAdjCreateNode(
56 const EditorRawDOMPoint
& aPoint
);
57 template nsresult
RangeUpdater::SelAdjInsertNode(const EditorDOMPoint
& aPoint
);
58 template nsresult
RangeUpdater::SelAdjInsertNode(
59 const EditorRawDOMPoint
& aPoint
);
61 void SelectionState::SaveSelection(Selection
& aSelection
) {
62 // if we need more items in the array, new them
63 if (mArray
.Length() < aSelection
.RangeCount()) {
64 for (uint32_t i
= mArray
.Length(); i
< aSelection
.RangeCount(); i
++) {
65 mArray
.AppendElement();
66 mArray
[i
] = new RangeItem();
68 } else if (mArray
.Length() > aSelection
.RangeCount()) {
69 // else if we have too many, delete them
70 mArray
.TruncateLength(aSelection
.RangeCount());
73 // now store the selection ranges
74 const uint32_t rangeCount
= aSelection
.RangeCount();
75 for (const uint32_t i
: IntegerRange(rangeCount
)) {
76 MOZ_ASSERT(aSelection
.RangeCount() == rangeCount
);
77 const nsRange
* range
= aSelection
.GetRangeAt(i
);
79 if (MOZ_UNLIKELY(NS_WARN_IF(!range
))) {
82 mArray
[i
]->StoreRange(*range
);
85 mDirection
= aSelection
.GetDirection();
88 nsresult
SelectionState::RestoreSelection(Selection
& aSelection
) {
89 // clear out selection
90 IgnoredErrorResult ignoredError
;
91 aSelection
.RemoveAllRanges(ignoredError
);
92 NS_WARNING_ASSERTION(!ignoredError
.Failed(),
93 "Selection::RemoveAllRanges() failed, but ignored");
95 aSelection
.SetDirection(mDirection
);
98 const CopyableAutoTArray
<RefPtr
<RangeItem
>, 10> rangeItems(mArray
);
99 for (const RefPtr
<RangeItem
>& rangeItem
: rangeItems
) {
100 RefPtr
<nsRange
> range
= rangeItem
->GetRange();
102 NS_WARNING("RangeItem::GetRange() failed");
103 return NS_ERROR_FAILURE
;
105 aSelection
.AddRangeAndSelectFramesAndNotifyListeners(*range
, error
);
106 if (error
.Failed()) {
108 "Selection::AddRangeAndSelectFramesAndNotifyListeners() failed");
109 return error
.StealNSResult();
115 bool SelectionState::Equals(const SelectionState
& aOther
) const {
116 if (mArray
.Length() != aOther
.mArray
.Length()) {
119 if (mArray
.IsEmpty()) {
120 return false; // XXX Why?
122 if (mDirection
!= aOther
.mDirection
) {
126 for (uint32_t i
: IntegerRange(mArray
.Length())) {
127 if (NS_WARN_IF(!mArray
[i
]) || NS_WARN_IF(!aOther
.mArray
[i
]) ||
128 !mArray
[i
]->Equals(*aOther
.mArray
[i
])) {
132 // if we got here, they are equal
136 /******************************************************************************
137 * mozilla::RangeUpdater
139 * Class for updating nsRanges in response to editor actions.
140 ******************************************************************************/
142 RangeUpdater::RangeUpdater() : mLocked(false) {}
144 void RangeUpdater::RegisterRangeItem(RangeItem
& aRangeItem
) {
145 if (mArray
.Contains(&aRangeItem
)) {
146 NS_ERROR("tried to register an already registered range");
147 return; // don't register it again. It would get doubly adjusted.
149 mArray
.AppendElement(&aRangeItem
);
152 void RangeUpdater::DropRangeItem(RangeItem
& aRangeItem
) {
153 NS_WARNING_ASSERTION(
154 mArray
.Contains(&aRangeItem
),
155 "aRangeItem is not in the range, but tried to removed from it");
156 mArray
.RemoveElement(&aRangeItem
);
159 void RangeUpdater::RegisterSelectionState(SelectionState
& aSelectionState
) {
160 for (RefPtr
<RangeItem
>& rangeItem
: aSelectionState
.mArray
) {
161 if (NS_WARN_IF(!rangeItem
)) {
164 RegisterRangeItem(*rangeItem
);
168 void RangeUpdater::DropSelectionState(SelectionState
& aSelectionState
) {
169 for (RefPtr
<RangeItem
>& rangeItem
: aSelectionState
.mArray
) {
170 if (NS_WARN_IF(!rangeItem
)) {
173 DropRangeItem(*rangeItem
);
179 template <typename PT
, typename CT
>
180 nsresult
RangeUpdater::SelAdjCreateNode(
181 const EditorDOMPointBase
<PT
, CT
>& aPoint
) {
183 // lock set by Will/DidReplaceParent, etc...
186 if (mArray
.IsEmpty()) {
190 if (NS_WARN_IF(!aPoint
.IsSetAndValid())) {
191 return NS_ERROR_INVALID_ARG
;
194 for (RefPtr
<RangeItem
>& rangeItem
: mArray
) {
195 if (NS_WARN_IF(!rangeItem
)) {
196 return NS_ERROR_FAILURE
;
198 if (rangeItem
->mStartContainer
== aPoint
.GetContainer() &&
199 rangeItem
->mStartOffset
> aPoint
.Offset()) {
200 rangeItem
->mStartOffset
++;
202 if (rangeItem
->mEndContainer
== aPoint
.GetContainer() &&
203 rangeItem
->mEndOffset
> aPoint
.Offset()) {
204 rangeItem
->mEndOffset
++;
210 template <typename PT
, typename CT
>
211 nsresult
RangeUpdater::SelAdjInsertNode(
212 const EditorDOMPointBase
<PT
, CT
>& aPoint
) {
213 nsresult rv
= SelAdjCreateNode(aPoint
);
214 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
215 "RangeUpdater::SelAdjCreateNode() failed");
219 void RangeUpdater::SelAdjDeleteNode(nsINode
& aNodeToDelete
) {
221 // lock set by Will/DidReplaceParent, etc...
225 if (mArray
.IsEmpty()) {
229 EditorRawDOMPoint
atNodeToDelete(&aNodeToDelete
);
230 NS_ASSERTION(atNodeToDelete
.IsSetAndValid(),
231 "aNodeToDelete must be an orphan node or this is called "
233 // check for range endpoints that are after aNodeToDelete and in the same
235 for (RefPtr
<RangeItem
>& rangeItem
: mArray
) {
236 MOZ_ASSERT(rangeItem
);
238 if (rangeItem
->mStartContainer
== atNodeToDelete
.GetContainer() &&
239 rangeItem
->mStartOffset
> atNodeToDelete
.Offset()) {
240 rangeItem
->mStartOffset
--;
242 if (rangeItem
->mEndContainer
== atNodeToDelete
.GetContainer() &&
243 rangeItem
->mEndOffset
> atNodeToDelete
.Offset()) {
244 rangeItem
->mEndOffset
--;
247 // check for range endpoints that are in aNodeToDelete
248 if (rangeItem
->mStartContainer
== &aNodeToDelete
) {
249 rangeItem
->mStartContainer
= atNodeToDelete
.GetContainer();
250 rangeItem
->mStartOffset
= atNodeToDelete
.Offset();
252 if (rangeItem
->mEndContainer
== &aNodeToDelete
) {
253 rangeItem
->mEndContainer
= atNodeToDelete
.GetContainer();
254 rangeItem
->mEndOffset
= atNodeToDelete
.Offset();
257 // check for range endpoints that are in descendants of aNodeToDelete
258 bool updateEndBoundaryToo
= false;
259 if (EditorUtils::IsDescendantOf(*rangeItem
->mStartContainer
,
261 updateEndBoundaryToo
=
262 rangeItem
->mStartContainer
== rangeItem
->mEndContainer
;
263 rangeItem
->mStartContainer
= atNodeToDelete
.GetContainer();
264 rangeItem
->mStartOffset
= atNodeToDelete
.Offset();
267 // avoid having to call IsDescendantOf() for common case of range startnode
269 if (updateEndBoundaryToo
||
270 EditorUtils::IsDescendantOf(*rangeItem
->mEndContainer
, aNodeToDelete
)) {
271 rangeItem
->mEndContainer
= atNodeToDelete
.GetContainer();
272 rangeItem
->mEndOffset
= atNodeToDelete
.Offset();
277 nsresult
RangeUpdater::SelAdjSplitNode(nsIContent
& aOriginalContent
,
278 uint32_t aSplitOffset
,
279 nsIContent
& aNewContent
,
280 SplitNodeDirection aSplitNodeDirection
) {
282 // lock set by Will/DidReplaceParent, etc...
286 if (mArray
.IsEmpty()) {
290 EditorRawDOMPoint
atNewNode(&aNewContent
);
291 nsresult rv
= SelAdjInsertNode(atNewNode
);
293 NS_WARNING("RangeUpdater::SelAdjInsertNode() failed");
297 // If point is in the range which are moved from aOriginalContent to
298 // aNewContent, we need to change its container to aNewContent and may need to
299 // adjust the offset. If point is in the range which are not moved from
300 // aOriginalContent, we may need to adjust the offset.
301 auto AdjustDOMPoint
= [&](nsCOMPtr
<nsINode
>& aContainer
,
302 uint32_t& aOffset
) -> void {
303 if (aContainer
!= &aOriginalContent
) {
306 if (aSplitNodeDirection
== SplitNodeDirection::LeftNodeIsNewOne
) {
307 if (aOffset
> aSplitOffset
) {
308 aOffset
-= aSplitOffset
;
310 aContainer
= &aNewContent
;
312 } else if (aOffset
>= aSplitOffset
) {
313 aContainer
= &aNewContent
;
314 aOffset
= aSplitOffset
- aOffset
;
318 for (RefPtr
<RangeItem
>& rangeItem
: mArray
) {
319 if (NS_WARN_IF(!rangeItem
)) {
320 return NS_ERROR_FAILURE
;
322 AdjustDOMPoint(rangeItem
->mStartContainer
, rangeItem
->mStartOffset
);
323 AdjustDOMPoint(rangeItem
->mEndContainer
, rangeItem
->mEndOffset
);
328 nsresult
RangeUpdater::SelAdjJoinNodes(
329 const EditorRawDOMPoint
& aStartOfRightContent
,
330 const nsIContent
& aRemovedContent
, uint32_t aOffsetOfRemovedContent
,
331 JoinNodesDirection aJoinNodesDirection
) {
332 MOZ_ASSERT(aStartOfRightContent
.IsSetAndValid());
335 // lock set by Will/DidReplaceParent, etc...
339 if (mArray
.IsEmpty()) {
343 auto AdjustDOMPoint
= [&](nsCOMPtr
<nsINode
>& aContainer
,
344 uint32_t& aOffset
) -> void {
345 if (aContainer
== aStartOfRightContent
.GetContainerParent()) {
346 // If the point is in common parent of joined content nodes and the
347 // point is after the removed point, decrease the offset.
348 if (aOffset
> aOffsetOfRemovedContent
) {
351 // If it pointed the removed content node, move to start of right content
352 // which was moved from the removed content.
353 else if (aOffset
== aOffsetOfRemovedContent
) {
354 aContainer
= aStartOfRightContent
.GetContainer();
355 aOffset
= aStartOfRightContent
.Offset();
357 } else if (aContainer
== aStartOfRightContent
.GetContainer()) {
358 // If the point is in joined node, and removed content is moved to
359 // start of the joined node, we need to adjust the offset.
360 if (aJoinNodesDirection
== JoinNodesDirection::LeftNodeIntoRightNode
) {
361 aOffset
+= aStartOfRightContent
.Offset();
363 } else if (aContainer
== &aRemovedContent
) {
364 // If the point is in the removed content, move the point to the new
365 // point in the joined node. If left node content is moved into
366 // right node, the offset should be same. Otherwise, we need to advance
367 // the offset to length of the removed content.
368 aContainer
= aStartOfRightContent
.GetContainer();
369 if (aJoinNodesDirection
== JoinNodesDirection::RightNodeIntoLeftNode
) {
370 aOffset
+= aStartOfRightContent
.Offset();
375 for (RefPtr
<RangeItem
>& rangeItem
: mArray
) {
376 if (NS_WARN_IF(!rangeItem
)) {
377 return NS_ERROR_FAILURE
;
379 AdjustDOMPoint(rangeItem
->mStartContainer
, rangeItem
->mStartOffset
);
380 AdjustDOMPoint(rangeItem
->mEndContainer
, rangeItem
->mEndOffset
);
386 void RangeUpdater::SelAdjReplaceText(const Text
& aTextNode
, uint32_t aOffset
,
387 uint32_t aReplacedLength
,
388 uint32_t aInsertedLength
) {
390 // lock set by Will/DidReplaceParent, etc...
394 // First, adjust selection for insertion because when offset is in the
395 // replaced range, it's adjusted to aOffset and never modified by the
396 // insertion if we adjust selection for deletion first.
397 SelAdjInsertText(aTextNode
, aOffset
, aInsertedLength
);
399 // Then, adjust selection for deletion.
400 SelAdjDeleteText(aTextNode
, aOffset
, aReplacedLength
);
403 void RangeUpdater::SelAdjInsertText(const Text
& aTextNode
, uint32_t aOffset
,
404 uint32_t aInsertedLength
) {
406 // lock set by Will/DidReplaceParent, etc...
410 for (RefPtr
<RangeItem
>& rangeItem
: mArray
) {
411 MOZ_ASSERT(rangeItem
);
413 if (rangeItem
->mStartContainer
== &aTextNode
&&
414 rangeItem
->mStartOffset
> aOffset
) {
415 rangeItem
->mStartOffset
+= aInsertedLength
;
417 if (rangeItem
->mEndContainer
== &aTextNode
&&
418 rangeItem
->mEndOffset
> aOffset
) {
419 rangeItem
->mEndOffset
+= aInsertedLength
;
424 void RangeUpdater::SelAdjDeleteText(const Text
& aTextNode
, uint32_t aOffset
,
425 uint32_t aDeletedLength
) {
427 // lock set by Will/DidReplaceParent, etc...
431 for (RefPtr
<RangeItem
>& rangeItem
: mArray
) {
432 MOZ_ASSERT(rangeItem
);
434 if (rangeItem
->mStartContainer
== &aTextNode
&&
435 rangeItem
->mStartOffset
> aOffset
) {
436 if (rangeItem
->mStartOffset
>= aDeletedLength
) {
437 rangeItem
->mStartOffset
-= aDeletedLength
;
439 rangeItem
->mStartOffset
= 0;
442 if (rangeItem
->mEndContainer
== &aTextNode
&&
443 rangeItem
->mEndOffset
> aOffset
) {
444 if (rangeItem
->mEndOffset
>= aDeletedLength
) {
445 rangeItem
->mEndOffset
-= aDeletedLength
;
447 rangeItem
->mEndOffset
= 0;
453 void RangeUpdater::DidReplaceContainer(const Element
& aRemovedElement
,
454 Element
& aInsertedElement
) {
455 if (NS_WARN_IF(!mLocked
)) {
460 for (RefPtr
<RangeItem
>& rangeItem
: mArray
) {
461 if (NS_WARN_IF(!rangeItem
)) {
465 if (rangeItem
->mStartContainer
== &aRemovedElement
) {
466 rangeItem
->mStartContainer
= &aInsertedElement
;
468 if (rangeItem
->mEndContainer
== &aRemovedElement
) {
469 rangeItem
->mEndContainer
= &aInsertedElement
;
474 void RangeUpdater::DidRemoveContainer(const Element
& aRemovedElement
,
475 nsINode
& aRemovedElementContainerNode
,
476 uint32_t aOldOffsetOfRemovedElement
,
477 uint32_t aOldChildCountOfRemovedElement
) {
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
= &aRemovedElementContainerNode
;
490 rangeItem
->mStartOffset
+= aOldOffsetOfRemovedElement
;
491 } else if (rangeItem
->mStartContainer
== &aRemovedElementContainerNode
&&
492 rangeItem
->mStartOffset
> aOldOffsetOfRemovedElement
) {
493 rangeItem
->mStartOffset
+= aOldChildCountOfRemovedElement
- 1;
496 if (rangeItem
->mEndContainer
== &aRemovedElement
) {
497 rangeItem
->mEndContainer
= &aRemovedElementContainerNode
;
498 rangeItem
->mEndOffset
+= aOldOffsetOfRemovedElement
;
499 } else if (rangeItem
->mEndContainer
== &aRemovedElementContainerNode
&&
500 rangeItem
->mEndOffset
> aOldOffsetOfRemovedElement
) {
501 rangeItem
->mEndOffset
+= aOldChildCountOfRemovedElement
- 1;
506 void RangeUpdater::DidMoveNode(const nsINode
& aOldParent
, uint32_t aOldOffset
,
507 const nsINode
& aNewParent
, uint32_t aNewOffset
) {
509 // Do nothing if moving nodes is occurred while changing the container.
512 for (RefPtr
<RangeItem
>& rangeItem
: mArray
) {
513 if (NS_WARN_IF(!rangeItem
)) {
517 // like a delete in aOldParent
518 if (rangeItem
->mStartContainer
== &aOldParent
&&
519 rangeItem
->mStartOffset
> aOldOffset
) {
520 rangeItem
->mStartOffset
--;
522 if (rangeItem
->mEndContainer
== &aOldParent
&&
523 rangeItem
->mEndOffset
> aOldOffset
) {
524 rangeItem
->mEndOffset
--;
527 // and like an insert in aNewParent
528 if (rangeItem
->mStartContainer
== &aNewParent
&&
529 rangeItem
->mStartOffset
> aNewOffset
) {
530 rangeItem
->mStartOffset
++;
532 if (rangeItem
->mEndContainer
== &aNewParent
&&
533 rangeItem
->mEndOffset
> aNewOffset
) {
534 rangeItem
->mEndOffset
++;
539 /******************************************************************************
542 * Helper struct for SelectionState. This stores range endpoints.
543 ******************************************************************************/
545 NS_IMPL_CYCLE_COLLECTION(RangeItem
, mStartContainer
, mEndContainer
)
546 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(RangeItem
, AddRef
)
547 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(RangeItem
, Release
)
549 void RangeItem::StoreRange(const nsRange
& aRange
) {
550 mStartContainer
= aRange
.GetStartContainer();
551 mStartOffset
= aRange
.StartOffset();
552 mEndContainer
= aRange
.GetEndContainer();
553 mEndOffset
= aRange
.EndOffset();
556 already_AddRefed
<nsRange
> RangeItem::GetRange() const {
557 RefPtr
<nsRange
> range
= nsRange::Create(
558 mStartContainer
, mStartOffset
, mEndContainer
, mEndOffset
, IgnoreErrors());
559 NS_WARNING_ASSERTION(range
, "nsRange::Create() failed");
560 return range
.forget();
563 } // namespace mozilla