Bug 1669129 - [devtools] Enable devtools.overflow.debugging.enabled. r=jdescottes
[gecko.git] / editor / libeditor / SelectionState.cpp
blob4171d141a5e103c0ed17c6cad0bb1ba6482c7d43
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
20 namespace mozilla {
22 using namespace dom;
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)) {
62 continue;
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);
79 ErrorResult error;
80 const CopyableAutoTArray<RefPtr<RangeItem>, 10> rangeItems(mArray);
81 for (const RefPtr<RangeItem>& rangeItem : rangeItems) {
82 RefPtr<nsRange> range = rangeItem->GetRange();
83 if (!range) {
84 NS_WARNING("RangeItem::GetRange() failed");
85 return NS_ERROR_FAILURE;
87 aSelection.AddRangeAndSelectFramesAndNotifyListeners(*range, error);
88 if (error.Failed()) {
89 NS_WARNING(
90 "Selection::AddRangeAndSelectFramesAndNotifyListeners() failed");
91 return error.StealNSResult();
94 return NS_OK;
97 bool SelectionState::IsCollapsed() const {
98 if (mArray.Length() != 1) {
99 return false;
101 RefPtr<nsRange> range = mArray[0]->GetRange();
102 if (!range) {
103 NS_WARNING("RangeItem::GetRange() failed");
104 return false;
106 return range->Collapsed();
109 bool SelectionState::Equals(SelectionState& aOther) const {
110 if (mArray.Length() != aOther.mArray.Length()) {
111 return false;
113 if (mArray.IsEmpty()) {
114 return false; // XXX Why?
116 if (mDirection != aOther.mDirection) {
117 return false;
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();
125 if (!range) {
126 NS_WARNING("Failed to create a range from the range item");
127 return false;
129 RefPtr<nsRange> otherRange = aOther.mArray[i]->GetRange();
130 if (!otherRange) {
131 NS_WARNING("Failed to create a range from the other's range item");
132 return false;
135 int16_t compResult = range->CompareBoundaryPoints(
136 Range_Binding::START_TO_START, *otherRange, ignoredError);
137 if (ignoredError.Failed()) {
138 NS_WARNING(
139 "nsRange::CompareBoundaryPoints(Range_Binding::START_TO_START) "
140 "failed");
141 return false;
143 if (compResult) {
144 return false;
146 compResult = range->CompareBoundaryPoints(Range_Binding::END_TO_END,
147 *otherRange, ignoredError);
148 if (ignoredError.Failed()) {
149 NS_WARNING(
150 "nsRange::CompareBoundaryPoints(Range_Binding::END_TO_END) failed");
151 return false;
153 if (compResult) {
154 return false;
157 // if we got here, they are equal
158 return true;
161 void SelectionState::Clear() {
162 // free any items in the array
163 mArray.Clear();
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)) {
195 continue;
197 RegisterRangeItem(*rangeItem);
201 void RangeUpdater::DropSelectionState(SelectionState& aSelectionState) {
202 for (RefPtr<RangeItem>& rangeItem : aSelectionState.mArray) {
203 if (NS_WARN_IF(!rangeItem)) {
204 continue;
206 DropRangeItem(*rangeItem);
210 // gravity methods:
212 template <typename PT, typename CT>
213 nsresult RangeUpdater::SelAdjCreateNode(
214 const EditorDOMPointBase<PT, CT>& aPoint) {
215 if (mLocked) {
216 // lock set by Will/DidReplaceParent, etc...
217 return NS_OK;
219 if (mArray.IsEmpty()) {
220 return NS_OK;
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++;
240 return NS_OK;
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");
249 return rv;
252 void RangeUpdater::SelAdjDeleteNode(nsINode& aNodeToDelete) {
253 if (mLocked) {
254 // lock set by Will/DidReplaceParent, etc...
255 return;
258 if (mArray.IsEmpty()) {
259 return;
262 EditorRawDOMPoint atNodeToDelete(&aNodeToDelete);
263 NS_ASSERTION(atNodeToDelete.IsSetAndValid(),
264 "aNodeToDelete must be an orphan node or this is called "
265 "during mutation");
266 // check for range endpoints that are after aNodeToDelete and in the same
267 // parent
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,
294 aNodeToDelete)) {
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
302 // == range endnode.
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) {
313 if (mLocked) {
314 // lock set by Will/DidReplaceParent, etc...
315 return NS_OK;
318 if (mArray.IsEmpty()) {
319 return NS_OK;
322 EditorRawDOMPoint atLeftNode(&aNewLeftNode);
323 nsresult rv = SelAdjInsertNode(atLeftNode);
324 if (NS_FAILED(rv)) {
325 NS_WARNING("RangeUpdater::SelAdjInsertNode() failed");
326 return rv;
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;
341 } else {
342 rangeItem->mStartContainer = &aNewLeftNode;
345 if (rangeItem->mEndContainer == &aRightNode) {
346 if (rangeItem->mEndOffset > lengthOfLeftNode) {
347 rangeItem->mEndOffset -= lengthOfLeftNode;
348 } else {
349 rangeItem->mEndContainer = &aNewLeftNode;
353 return NS_OK;
356 nsresult RangeUpdater::SelAdjJoinNodes(nsINode& aLeftNode, nsINode& aRightNode,
357 nsINode& aParent, int32_t aOffset,
358 int32_t aOldLeftNodeLength) {
359 if (mLocked) {
360 // lock set by Will/DidReplaceParent, etc...
361 return NS_OK;
364 if (mArray.IsEmpty()) {
365 return NS_OK;
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;
408 return NS_OK;
411 void RangeUpdater::SelAdjReplaceText(const Text& aTextNode, int32_t aOffset,
412 int32_t aReplacedLength,
413 int32_t aInsertedLength) {
414 if (mLocked) {
415 // lock set by Will/DidReplaceParent, etc...
416 return;
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) {
430 if (mLocked) {
431 // lock set by Will/DidReplaceParent, etc...
432 return;
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) {
451 if (mLocked) {
452 // lock set by Will/DidReplaceParent, etc...
453 return;
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)) {
479 return;
481 mLocked = false;
483 for (RefPtr<RangeItem>& rangeItem : mArray) {
484 if (NS_WARN_IF(!rangeItem)) {
485 return;
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)) {
502 return;
504 mLocked = false;
506 for (RefPtr<RangeItem>& rangeItem : mArray) {
507 if (NS_WARN_IF(!rangeItem)) {
508 return;
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)) {
534 return;
536 mLocked = false;
538 for (RefPtr<RangeItem>& rangeItem : mArray) {
539 if (NS_WARN_IF(!rangeItem)) {
540 return;
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 /******************************************************************************
566 * mozilla::RangeItem
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