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/. */
5
6 #include "SelectionState.h"
7
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
19
20 namespace mozilla {
21
22 using namespace dom;
23
24 /******************************************************************************
25 * mozilla::SelectionState
26 *
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 ******************************************************************************/
31
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);
38
SelectionState()39 SelectionState::SelectionState() : mDirection(eDirNext) {}
40
SaveSelection(Selection & aSelection)41 void SelectionState::SaveSelection(Selection& aSelection) {
42 // if we need more items in the array, new them
43 if (mArray.Length() < aSelection.RangeCount()) {
44 for (uint32_t i = mArray.Length(); i < aSelection.RangeCount(); i++) {
45 mArray.AppendElement();
46 mArray[i] = new RangeItem();
47 }
48 } else if (mArray.Length() > aSelection.RangeCount()) {
49 // else if we have too many, delete them
50 mArray.TruncateLength(aSelection.RangeCount());
51 }
52
53 // now store the selection ranges
54 for (uint32_t i = 0; i < aSelection.RangeCount(); i++) {
55 const nsRange* range = aSelection.GetRangeAt(i);
56 if (NS_WARN_IF(!range)) {
57 continue;
58 }
59 mArray[i]->StoreRange(*range);
60 }
61
62 mDirection = aSelection.GetDirection();
63 }
64
RestoreSelection(Selection & aSelection)65 nsresult SelectionState::RestoreSelection(Selection& aSelection) {
66 // clear out selection
67 IgnoredErrorResult ignoredError;
68 aSelection.RemoveAllRanges(ignoredError);
69 NS_WARNING_ASSERTION(!ignoredError.Failed(),
70 "Selection::RemoveAllRanges() failed, but ignored");
71
72 aSelection.SetDirection(mDirection);
73
74 ErrorResult error;
75 const CopyableAutoTArray<RefPtr<RangeItem>, 10> rangeItems(mArray);
76 for (const RefPtr<RangeItem>& rangeItem : rangeItems) {
77 RefPtr<nsRange> range = rangeItem->GetRange();
78 if (!range) {
79 NS_WARNING("RangeItem::GetRange() failed");
80 return NS_ERROR_FAILURE;
81 }
82 aSelection.AddRangeAndSelectFramesAndNotifyListeners(*range, error);
83 if (error.Failed()) {
84 NS_WARNING(
85 "Selection::AddRangeAndSelectFramesAndNotifyListeners() failed");
86 return error.StealNSResult();
87 }
88 }
89 return NS_OK;
90 }
91
IsCollapsed() const92 bool SelectionState::IsCollapsed() const {
93 if (mArray.Length() != 1) {
94 return false;
95 }
96 RefPtr<nsRange> range = mArray[0]->GetRange();
97 if (!range) {
98 NS_WARNING("RangeItem::GetRange() failed");
99 return false;
100 }
101 return range->Collapsed();
102 }
103
Equals(SelectionState & aOther) const104 bool SelectionState::Equals(SelectionState& aOther) const {
105 if (mArray.Length() != aOther.mArray.Length()) {
106 return false;
107 }
108 if (mArray.IsEmpty()) {
109 return false; // XXX Why?
110 }
111 if (mDirection != aOther.mDirection) {
112 return false;
113 }
114
115 // XXX Creating nsRanges are really expensive. Why cannot we just check
116 // the container and offsets??
117 IgnoredErrorResult ignoredError;
118 for (size_t i = 0; i < mArray.Length(); i++) {
119 RefPtr<nsRange> range = mArray[i]->GetRange();
120 if (!range) {
121 NS_WARNING("Failed to create a range from the range item");
122 return false;
123 }
124 RefPtr<nsRange> otherRange = aOther.mArray[i]->GetRange();
125 if (!otherRange) {
126 NS_WARNING("Failed to create a range from the other's range item");
127 return false;
128 }
129
130 int16_t compResult = range->CompareBoundaryPoints(
131 Range_Binding::START_TO_START, *otherRange, ignoredError);
132 if (ignoredError.Failed()) {
133 NS_WARNING(
134 "nsRange::CompareBoundaryPoints(Range_Binding::START_TO_START) "
135 "failed");
136 return false;
137 }
138 if (compResult) {
139 return false;
140 }
141 compResult = range->CompareBoundaryPoints(Range_Binding::END_TO_END,
142 *otherRange, ignoredError);
143 if (ignoredError.Failed()) {
144 NS_WARNING(
145 "nsRange::CompareBoundaryPoints(Range_Binding::END_TO_END) failed");
146 return false;
147 }
148 if (compResult) {
149 return false;
150 }
151 }
152 // if we got here, they are equal
153 return true;
154 }
155
Clear()156 void SelectionState::Clear() {
157 // free any items in the array
158 mArray.Clear();
159 mDirection = eDirNext;
160 }
161
IsEmpty() const162 bool SelectionState::IsEmpty() const { return mArray.IsEmpty(); }
163
164 /******************************************************************************
165 * mozilla::RangeUpdater
166 *
167 * Class for updating nsRanges in response to editor actions.
168 ******************************************************************************/
169
RangeUpdater()170 RangeUpdater::RangeUpdater() : mLocked(false) {}
171
RegisterRangeItem(RangeItem & aRangeItem)172 void RangeUpdater::RegisterRangeItem(RangeItem& aRangeItem) {
173 if (mArray.Contains(&aRangeItem)) {
174 NS_ERROR("tried to register an already registered range");
175 return; // don't register it again. It would get doubly adjusted.
176 }
177 mArray.AppendElement(&aRangeItem);
178 }
179
DropRangeItem(RangeItem & aRangeItem)180 void RangeUpdater::DropRangeItem(RangeItem& aRangeItem) {
181 NS_WARNING_ASSERTION(
182 mArray.Contains(&aRangeItem),
183 "aRangeItem is not in the range, but tried to removed from it");
184 mArray.RemoveElement(&aRangeItem);
185 }
186
RegisterSelectionState(SelectionState & aSelectionState)187 void RangeUpdater::RegisterSelectionState(SelectionState& aSelectionState) {
188 for (RefPtr<RangeItem>& rangeItem : aSelectionState.mArray) {
189 if (NS_WARN_IF(!rangeItem)) {
190 continue;
191 }
192 RegisterRangeItem(*rangeItem);
193 }
194 }
195
DropSelectionState(SelectionState & aSelectionState)196 void RangeUpdater::DropSelectionState(SelectionState& aSelectionState) {
197 for (RefPtr<RangeItem>& rangeItem : aSelectionState.mArray) {
198 if (NS_WARN_IF(!rangeItem)) {
199 continue;
200 }
201 DropRangeItem(*rangeItem);
202 }
203 }
204
205 // gravity methods:
206
207 template <typename PT, typename CT>
SelAdjCreateNode(const EditorDOMPointBase<PT,CT> & aPoint)208 nsresult RangeUpdater::SelAdjCreateNode(
209 const EditorDOMPointBase<PT, CT>& aPoint) {
210 if (mLocked) {
211 // lock set by Will/DidReplaceParent, etc...
212 return NS_OK;
213 }
214 if (mArray.IsEmpty()) {
215 return NS_OK;
216 }
217
218 if (NS_WARN_IF(!aPoint.IsSetAndValid())) {
219 return NS_ERROR_INVALID_ARG;
220 }
221
222 for (RefPtr<RangeItem>& rangeItem : mArray) {
223 if (NS_WARN_IF(!rangeItem)) {
224 return NS_ERROR_FAILURE;
225 }
226 if (rangeItem->mStartContainer == aPoint.GetContainer() &&
227 rangeItem->mStartOffset > aPoint.Offset()) {
228 rangeItem->mStartOffset++;
229 }
230 if (rangeItem->mEndContainer == aPoint.GetContainer() &&
231 rangeItem->mEndOffset > aPoint.Offset()) {
232 rangeItem->mEndOffset++;
233 }
234 }
235 return NS_OK;
236 }
237
238 template <typename PT, typename CT>
SelAdjInsertNode(const EditorDOMPointBase<PT,CT> & aPoint)239 nsresult RangeUpdater::SelAdjInsertNode(
240 const EditorDOMPointBase<PT, CT>& aPoint) {
241 nsresult rv = SelAdjCreateNode(aPoint);
242 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
243 "RangeUpdater::SelAdjCreateNode() failed");
244 return rv;
245 }
246
SelAdjDeleteNode(nsINode & aNodeToDelete)247 void RangeUpdater::SelAdjDeleteNode(nsINode& aNodeToDelete) {
248 if (mLocked) {
249 // lock set by Will/DidReplaceParent, etc...
250 return;
251 }
252
253 if (mArray.IsEmpty()) {
254 return;
255 }
256
257 EditorRawDOMPoint atNodeToDelete(&aNodeToDelete);
258 NS_ASSERTION(atNodeToDelete.IsSetAndValid(),
259 "aNodeToDelete must be an orphan node or this is called "
260 "during mutation");
261 // check for range endpoints that are after aNodeToDelete and in the same
262 // parent
263 for (RefPtr<RangeItem>& rangeItem : mArray) {
264 MOZ_ASSERT(rangeItem);
265
266 if (rangeItem->mStartContainer == atNodeToDelete.GetContainer() &&
267 rangeItem->mStartOffset > atNodeToDelete.Offset()) {
268 rangeItem->mStartOffset--;
269 }
270 if (rangeItem->mEndContainer == atNodeToDelete.GetContainer() &&
271 rangeItem->mEndOffset > atNodeToDelete.Offset()) {
272 rangeItem->mEndOffset--;
273 }
274
275 // check for range endpoints that are in aNodeToDelete
276 if (rangeItem->mStartContainer == &aNodeToDelete) {
277 rangeItem->mStartContainer = atNodeToDelete.GetContainer();
278 rangeItem->mStartOffset = atNodeToDelete.Offset();
279 }
280 if (rangeItem->mEndContainer == &aNodeToDelete) {
281 rangeItem->mEndContainer = atNodeToDelete.GetContainer();
282 rangeItem->mEndOffset = atNodeToDelete.Offset();
283 }
284
285 // check for range endpoints that are in descendants of aNodeToDelete
286 bool updateEndBoundaryToo = false;
287 if (EditorUtils::IsDescendantOf(*rangeItem->mStartContainer,
288 aNodeToDelete)) {
289 updateEndBoundaryToo =
290 rangeItem->mStartContainer == rangeItem->mEndContainer;
291 rangeItem->mStartContainer = atNodeToDelete.GetContainer();
292 rangeItem->mStartOffset = atNodeToDelete.Offset();
293 }
294
295 // avoid having to call IsDescendantOf() for common case of range startnode
296 // == range endnode.
297 if (updateEndBoundaryToo ||
298 EditorUtils::IsDescendantOf(*rangeItem->mEndContainer, aNodeToDelete)) {
299 rangeItem->mEndContainer = atNodeToDelete.GetContainer();
300 rangeItem->mEndOffset = atNodeToDelete.Offset();
301 }
302 }
303 }
304
SelAdjSplitNode(nsIContent & aRightNode,nsIContent & aNewLeftNode)305 nsresult RangeUpdater::SelAdjSplitNode(nsIContent& aRightNode,
306 nsIContent& aNewLeftNode) {
307 if (mLocked) {
308 // lock set by Will/DidReplaceParent, etc...
309 return NS_OK;
310 }
311
312 if (mArray.IsEmpty()) {
313 return NS_OK;
314 }
315
316 EditorRawDOMPoint atLeftNode(&aNewLeftNode);
317 nsresult rv = SelAdjInsertNode(atLeftNode);
318 if (NS_FAILED(rv)) {
319 NS_WARNING("RangeUpdater::SelAdjInsertNode() failed");
320 return rv;
321 }
322
323 // If point in the ranges is in left node, change its container to the left
324 // node. If point in the ranges is in right node, subtract numbers of
325 // children moved to left node from the offset.
326 uint32_t lengthOfLeftNode = aNewLeftNode.Length();
327 for (RefPtr<RangeItem>& rangeItem : mArray) {
328 if (NS_WARN_IF(!rangeItem)) {
329 return NS_ERROR_FAILURE;
330 }
331
332 if (rangeItem->mStartContainer == &aRightNode) {
333 if (rangeItem->mStartOffset > lengthOfLeftNode) {
334 rangeItem->mStartOffset -= lengthOfLeftNode;
335 } else {
336 rangeItem->mStartContainer = &aNewLeftNode;
337 }
338 }
339 if (rangeItem->mEndContainer == &aRightNode) {
340 if (rangeItem->mEndOffset > lengthOfLeftNode) {
341 rangeItem->mEndOffset -= lengthOfLeftNode;
342 } else {
343 rangeItem->mEndContainer = &aNewLeftNode;
344 }
345 }
346 }
347 return NS_OK;
348 }
349
SelAdjJoinNodes(nsINode & aLeftNode,nsINode & aRightNode,nsINode & aParent,uint32_t aOffset,uint32_t aOldLeftNodeLength)350 nsresult RangeUpdater::SelAdjJoinNodes(nsINode& aLeftNode, nsINode& aRightNode,
351 nsINode& aParent, uint32_t aOffset,
352 uint32_t aOldLeftNodeLength) {
353 if (mLocked) {
354 // lock set by Will/DidReplaceParent, etc...
355 return NS_OK;
356 }
357
358 if (mArray.IsEmpty()) {
359 return NS_OK;
360 }
361
362 for (RefPtr<RangeItem>& rangeItem : mArray) {
363 if (NS_WARN_IF(!rangeItem)) {
364 return NS_ERROR_FAILURE;
365 }
366
367 if (rangeItem->mStartContainer == &aParent) {
368 // adjust start point in aParent
369 if (rangeItem->mStartOffset > aOffset) {
370 rangeItem->mStartOffset--;
371 } else if (rangeItem->mStartOffset == aOffset) {
372 // join keeps right hand node
373 rangeItem->mStartContainer = &aRightNode;
374 rangeItem->mStartOffset = aOldLeftNodeLength;
375 }
376 } else if (rangeItem->mStartContainer == &aRightNode) {
377 // adjust start point in aRightNode
378 rangeItem->mStartOffset += aOldLeftNodeLength;
379 } else if (rangeItem->mStartContainer == &aLeftNode) {
380 // adjust start point in aLeftNode
381 rangeItem->mStartContainer = &aRightNode;
382 }
383
384 if (rangeItem->mEndContainer == &aParent) {
385 // adjust end point in aParent
386 if (rangeItem->mEndOffset > aOffset) {
387 rangeItem->mEndOffset--;
388 } else if (rangeItem->mEndOffset == aOffset) {
389 // join keeps right hand node
390 rangeItem->mEndContainer = &aRightNode;
391 rangeItem->mEndOffset = aOldLeftNodeLength;
392 }
393 } else if (rangeItem->mEndContainer == &aRightNode) {
394 // adjust end point in aRightNode
395 rangeItem->mEndOffset += aOldLeftNodeLength;
396 } else if (rangeItem->mEndContainer == &aLeftNode) {
397 // adjust end point in aLeftNode
398 rangeItem->mEndContainer = &aRightNode;
399 }
400 }
401
402 return NS_OK;
403 }
404
SelAdjReplaceText(const Text & aTextNode,uint32_t aOffset,uint32_t aReplacedLength,uint32_t aInsertedLength)405 void RangeUpdater::SelAdjReplaceText(const Text& aTextNode, uint32_t aOffset,
406 uint32_t aReplacedLength,
407 uint32_t aInsertedLength) {
408 if (mLocked) {
409 // lock set by Will/DidReplaceParent, etc...
410 return;
411 }
412
413 // First, adjust selection for insertion because when offset is in the
414 // replaced range, it's adjusted to aOffset and never modified by the
415 // insertion if we adjust selection for deletion first.
416 SelAdjInsertText(aTextNode, aOffset, aInsertedLength);
417
418 // Then, adjust selection for deletion.
419 SelAdjDeleteText(aTextNode, aOffset, aReplacedLength);
420 }
421
SelAdjInsertText(const Text & aTextNode,uint32_t aOffset,uint32_t aInsertedLength)422 void RangeUpdater::SelAdjInsertText(const Text& aTextNode, uint32_t aOffset,
423 uint32_t aInsertedLength) {
424 if (mLocked) {
425 // lock set by Will/DidReplaceParent, etc...
426 return;
427 }
428
429 for (RefPtr<RangeItem>& rangeItem : mArray) {
430 MOZ_ASSERT(rangeItem);
431
432 if (rangeItem->mStartContainer == &aTextNode &&
433 rangeItem->mStartOffset > aOffset) {
434 rangeItem->mStartOffset += aInsertedLength;
435 }
436 if (rangeItem->mEndContainer == &aTextNode &&
437 rangeItem->mEndOffset > aOffset) {
438 rangeItem->mEndOffset += aInsertedLength;
439 }
440 }
441 }
442
SelAdjDeleteText(const Text & aTextNode,uint32_t aOffset,uint32_t aDeletedLength)443 void RangeUpdater::SelAdjDeleteText(const Text& aTextNode, uint32_t aOffset,
444 uint32_t aDeletedLength) {
445 if (mLocked) {
446 // lock set by Will/DidReplaceParent, etc...
447 return;
448 }
449
450 for (RefPtr<RangeItem>& rangeItem : mArray) {
451 MOZ_ASSERT(rangeItem);
452
453 if (rangeItem->mStartContainer == &aTextNode &&
454 rangeItem->mStartOffset > aOffset) {
455 if (rangeItem->mStartOffset >= aDeletedLength) {
456 rangeItem->mStartOffset -= aDeletedLength;
457 } else {
458 rangeItem->mStartOffset = 0;
459 }
460 }
461 if (rangeItem->mEndContainer == &aTextNode &&
462 rangeItem->mEndOffset > aOffset) {
463 if (rangeItem->mEndOffset >= aDeletedLength) {
464 rangeItem->mEndOffset -= aDeletedLength;
465 } else {
466 rangeItem->mEndOffset = 0;
467 }
468 }
469 }
470 }
471
DidReplaceContainer(const Element & aRemovedElement,Element & aInsertedElement)472 void RangeUpdater::DidReplaceContainer(const Element& aRemovedElement,
473 Element& aInsertedElement) {
474 if (NS_WARN_IF(!mLocked)) {
475 return;
476 }
477 mLocked = false;
478
479 for (RefPtr<RangeItem>& rangeItem : mArray) {
480 if (NS_WARN_IF(!rangeItem)) {
481 return;
482 }
483
484 if (rangeItem->mStartContainer == &aRemovedElement) {
485 rangeItem->mStartContainer = &aInsertedElement;
486 }
487 if (rangeItem->mEndContainer == &aRemovedElement) {
488 rangeItem->mEndContainer = &aInsertedElement;
489 }
490 }
491 }
492
DidRemoveContainer(const Element & aRemovedElement,nsINode & aRemovedElementContainerNode,uint32_t aOldOffsetOfRemovedElement,uint32_t aOldChildCountOfRemovedElement)493 void RangeUpdater::DidRemoveContainer(const Element& aRemovedElement,
494 nsINode& aRemovedElementContainerNode,
495 uint32_t aOldOffsetOfRemovedElement,
496 uint32_t aOldChildCountOfRemovedElement) {
497 if (NS_WARN_IF(!mLocked)) {
498 return;
499 }
500 mLocked = false;
501
502 for (RefPtr<RangeItem>& rangeItem : mArray) {
503 if (NS_WARN_IF(!rangeItem)) {
504 return;
505 }
506
507 if (rangeItem->mStartContainer == &aRemovedElement) {
508 rangeItem->mStartContainer = &aRemovedElementContainerNode;
509 rangeItem->mStartOffset += aOldOffsetOfRemovedElement;
510 } else if (rangeItem->mStartContainer == &aRemovedElementContainerNode &&
511 rangeItem->mStartOffset > aOldOffsetOfRemovedElement) {
512 rangeItem->mStartOffset += aOldChildCountOfRemovedElement - 1;
513 }
514
515 if (rangeItem->mEndContainer == &aRemovedElement) {
516 rangeItem->mEndContainer = &aRemovedElementContainerNode;
517 rangeItem->mEndOffset += aOldOffsetOfRemovedElement;
518 } else if (rangeItem->mEndContainer == &aRemovedElementContainerNode &&
519 rangeItem->mEndOffset > aOldOffsetOfRemovedElement) {
520 rangeItem->mEndOffset += aOldChildCountOfRemovedElement - 1;
521 }
522 }
523 }
524
DidMoveNode(const nsINode & aOldParent,uint32_t aOldOffset,const nsINode & aNewParent,uint32_t aNewOffset)525 void RangeUpdater::DidMoveNode(const nsINode& aOldParent, uint32_t aOldOffset,
526 const nsINode& aNewParent, uint32_t aNewOffset) {
527 if (NS_WARN_IF(!mLocked)) {
528 return;
529 }
530 mLocked = false;
531
532 for (RefPtr<RangeItem>& rangeItem : mArray) {
533 if (NS_WARN_IF(!rangeItem)) {
534 return;
535 }
536
537 // like a delete in aOldParent
538 if (rangeItem->mStartContainer == &aOldParent &&
539 rangeItem->mStartOffset > aOldOffset) {
540 rangeItem->mStartOffset--;
541 }
542 if (rangeItem->mEndContainer == &aOldParent &&
543 rangeItem->mEndOffset > aOldOffset) {
544 rangeItem->mEndOffset--;
545 }
546
547 // and like an insert in aNewParent
548 if (rangeItem->mStartContainer == &aNewParent &&
549 rangeItem->mStartOffset > aNewOffset) {
550 rangeItem->mStartOffset++;
551 }
552 if (rangeItem->mEndContainer == &aNewParent &&
553 rangeItem->mEndOffset > aNewOffset) {
554 rangeItem->mEndOffset++;
555 }
556 }
557 }
558
559 /******************************************************************************
560 * mozilla::RangeItem
561 *
562 * Helper struct for SelectionState. This stores range endpoints.
563 ******************************************************************************/
564
NS_IMPL_CYCLE_COLLECTION(RangeItem,mStartContainer,mEndContainer)565 NS_IMPL_CYCLE_COLLECTION(RangeItem, mStartContainer, mEndContainer)
566 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(RangeItem, AddRef)
567 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(RangeItem, Release)
568
569 void RangeItem::StoreRange(const nsRange& aRange) {
570 mStartContainer = aRange.GetStartContainer();
571 mStartOffset = aRange.StartOffset();
572 mEndContainer = aRange.GetEndContainer();
573 mEndOffset = aRange.EndOffset();
574 }
575
GetRange()576 already_AddRefed<nsRange> RangeItem::GetRange() {
577 RefPtr<nsRange> range = nsRange::Create(
578 mStartContainer, mStartOffset, mEndContainer, mEndOffset, IgnoreErrors());
579 NS_WARNING_ASSERTION(range, "nsRange::Create() failed");
580 return range.forget();
581 }
582
583 } // namespace mozilla
584