1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 #include "mozilla/AsyncEventDispatcher.h"
8 #include "mozilla/dom/Element.h"
9 #include "nsCOMPtr.h"
10 #include "nsTreeSelection.h"
11 #include "XULTreeElement.h"
12 #include "nsITreeView.h"
13 #include "nsString.h"
14 #include "nsIContent.h"
15 #include "nsNameSpaceManager.h"
16 #include "nsGkAtoms.h"
17 #include "nsComponentManagerUtils.h"
18 #include "nsTreeColumns.h"
19 
20 using namespace mozilla;
21 using dom::XULTreeElement;
22 
23 // A helper class for managing our ranges of selection.
24 struct nsTreeRange {
25   nsTreeSelection* mSelection;
26 
27   nsTreeRange* mPrev;
28   nsTreeRange* mNext;
29 
30   int32_t mMin;
31   int32_t mMax;
32 
nsTreeRangensTreeRange33   nsTreeRange(nsTreeSelection* aSel, int32_t aSingleVal)
34       : mSelection(aSel),
35         mPrev(nullptr),
36         mNext(nullptr),
37         mMin(aSingleVal),
38         mMax(aSingleVal) {}
nsTreeRangensTreeRange39   nsTreeRange(nsTreeSelection* aSel, int32_t aMin, int32_t aMax)
40       : mSelection(aSel),
41         mPrev(nullptr),
42         mNext(nullptr),
43         mMin(aMin),
44         mMax(aMax) {}
45 
~nsTreeRangensTreeRange46   ~nsTreeRange() { delete mNext; }
47 
ConnectnsTreeRange48   void Connect(nsTreeRange* aPrev = nullptr, nsTreeRange* aNext = nullptr) {
49     if (aPrev)
50       aPrev->mNext = this;
51     else
52       mSelection->mFirstRange = this;
53 
54     if (aNext) aNext->mPrev = this;
55 
56     mPrev = aPrev;
57     mNext = aNext;
58   }
59 
RemoveRangensTreeRange60   nsresult RemoveRange(int32_t aStart, int32_t aEnd) {
61     // This should so be a loop... sigh...
62     // We start past the range to remove, so no more to remove
63     if (aEnd < mMin) return NS_OK;
64     // We are the last range to be affected
65     if (aEnd < mMax) {
66       if (aStart <= mMin) {
67         // Just chop the start of the range off
68         mMin = aEnd + 1;
69       } else {
70         // We need to split the range
71         nsTreeRange* range = new nsTreeRange(mSelection, aEnd + 1, mMax);
72         if (!range) return NS_ERROR_OUT_OF_MEMORY;
73 
74         mMax = aStart - 1;
75         range->Connect(this, mNext);
76       }
77       return NS_OK;
78     }
79     nsTreeRange* next = mNext;
80     if (aStart <= mMin) {
81       // The remove includes us, remove ourselves from the list
82       if (mPrev)
83         mPrev->mNext = next;
84       else
85         mSelection->mFirstRange = next;
86 
87       if (next) next->mPrev = mPrev;
88       mPrev = mNext = nullptr;
89       delete this;
90     } else if (aStart <= mMax) {
91       // Just chop the end of the range off
92       mMax = aStart - 1;
93     }
94     return next ? next->RemoveRange(aStart, aEnd) : NS_OK;
95   }
96 
RemovensTreeRange97   nsresult Remove(int32_t aIndex) {
98     if (aIndex >= mMin && aIndex <= mMax) {
99       // We have found the range that contains us.
100       if (mMin == mMax) {
101         // Delete the whole range.
102         if (mPrev) mPrev->mNext = mNext;
103         if (mNext) mNext->mPrev = mPrev;
104         nsTreeRange* first = mSelection->mFirstRange;
105         if (first == this) mSelection->mFirstRange = mNext;
106         mNext = mPrev = nullptr;
107         delete this;
108       } else if (aIndex == mMin)
109         mMin++;
110       else if (aIndex == mMax)
111         mMax--;
112       else {
113         // We have to break this range.
114         nsTreeRange* newRange = new nsTreeRange(mSelection, aIndex + 1, mMax);
115         if (!newRange) return NS_ERROR_OUT_OF_MEMORY;
116 
117         newRange->Connect(this, mNext);
118         mMax = aIndex - 1;
119       }
120     } else if (mNext)
121       return mNext->Remove(aIndex);
122 
123     return NS_OK;
124   }
125 
AddnsTreeRange126   nsresult Add(int32_t aIndex) {
127     if (aIndex < mMin) {
128       // We have found a spot to insert.
129       if (aIndex + 1 == mMin)
130         mMin = aIndex;
131       else if (mPrev && mPrev->mMax + 1 == aIndex)
132         mPrev->mMax = aIndex;
133       else {
134         // We have to create a new range.
135         nsTreeRange* newRange = new nsTreeRange(mSelection, aIndex);
136         if (!newRange) return NS_ERROR_OUT_OF_MEMORY;
137 
138         newRange->Connect(mPrev, this);
139       }
140     } else if (mNext)
141       mNext->Add(aIndex);
142     else {
143       // Insert on to the end.
144       if (mMax + 1 == aIndex)
145         mMax = aIndex;
146       else {
147         // We have to create a new range.
148         nsTreeRange* newRange = new nsTreeRange(mSelection, aIndex);
149         if (!newRange) return NS_ERROR_OUT_OF_MEMORY;
150 
151         newRange->Connect(this, nullptr);
152       }
153     }
154     return NS_OK;
155   }
156 
ContainsnsTreeRange157   bool Contains(int32_t aIndex) {
158     if (aIndex >= mMin && aIndex <= mMax) return true;
159 
160     if (mNext) return mNext->Contains(aIndex);
161 
162     return false;
163   }
164 
CountnsTreeRange165   int32_t Count() {
166     int32_t total = mMax - mMin + 1;
167     if (mNext) total += mNext->Count();
168     return total;
169   }
170 
CollectRangesnsTreeRange171   static void CollectRanges(nsTreeRange* aRange, nsTArray<int32_t>& aRanges) {
172     nsTreeRange* cur = aRange;
173     while (cur) {
174       aRanges.AppendElement(cur->mMin);
175       aRanges.AppendElement(cur->mMax);
176       cur = cur->mNext;
177     }
178   }
179 
InvalidateRangesnsTreeRange180   static void InvalidateRanges(XULTreeElement* aTree,
181                                nsTArray<int32_t>& aRanges) {
182     if (aTree) {
183       RefPtr<nsXULElement> tree = aTree;
184       for (uint32_t i = 0; i < aRanges.Length(); i += 2) {
185         aTree->InvalidateRange(aRanges[i], aRanges[i + 1]);
186       }
187     }
188   }
189 
InvalidatensTreeRange190   void Invalidate() {
191     nsTArray<int32_t> ranges;
192     CollectRanges(this, ranges);
193     InvalidateRanges(mSelection->mTree, ranges);
194   }
195 
RemoveAllButnsTreeRange196   void RemoveAllBut(int32_t aIndex) {
197     if (aIndex >= mMin && aIndex <= mMax) {
198       // Invalidate everything in this list.
199       nsTArray<int32_t> ranges;
200       CollectRanges(mSelection->mFirstRange, ranges);
201 
202       mMin = aIndex;
203       mMax = aIndex;
204 
205       nsTreeRange* first = mSelection->mFirstRange;
206       if (mPrev) mPrev->mNext = mNext;
207       if (mNext) mNext->mPrev = mPrev;
208       mNext = mPrev = nullptr;
209 
210       if (first != this) {
211         delete mSelection->mFirstRange;
212         mSelection->mFirstRange = this;
213       }
214       InvalidateRanges(mSelection->mTree, ranges);
215     } else if (mNext)
216       mNext->RemoveAllBut(aIndex);
217   }
218 
InsertnsTreeRange219   void Insert(nsTreeRange* aRange) {
220     if (mMin >= aRange->mMax)
221       aRange->Connect(mPrev, this);
222     else if (mNext)
223       mNext->Insert(aRange);
224     else
225       aRange->Connect(this, nullptr);
226   }
227 };
228 
nsTreeSelection(XULTreeElement * aTree)229 nsTreeSelection::nsTreeSelection(XULTreeElement* aTree)
230     : mTree(aTree),
231       mSuppressed(false),
232       mCurrentIndex(-1),
233       mShiftSelectPivot(-1),
234       mFirstRange(nullptr) {}
235 
~nsTreeSelection()236 nsTreeSelection::~nsTreeSelection() {
237   delete mFirstRange;
238   if (mSelectTimer) mSelectTimer->Cancel();
239 }
240 
NS_IMPL_CYCLE_COLLECTION(nsTreeSelection,mTree)241 NS_IMPL_CYCLE_COLLECTION(nsTreeSelection, mTree)
242 
243 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsTreeSelection)
244 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsTreeSelection)
245 
246 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsTreeSelection)
247   NS_INTERFACE_MAP_ENTRY(nsITreeSelection)
248   NS_INTERFACE_MAP_ENTRY(nsINativeTreeSelection)
249   NS_INTERFACE_MAP_ENTRY(nsISupports)
250 NS_INTERFACE_MAP_END
251 
252 NS_IMETHODIMP nsTreeSelection::GetTree(XULTreeElement** aTree) {
253   NS_IF_ADDREF(*aTree = mTree);
254   return NS_OK;
255 }
256 
SetTree(XULTreeElement * aTree)257 NS_IMETHODIMP nsTreeSelection::SetTree(XULTreeElement* aTree) {
258   if (mSelectTimer) {
259     mSelectTimer->Cancel();
260     mSelectTimer = nullptr;
261   }
262 
263   mTree = aTree;
264   return NS_OK;
265 }
266 
GetSingle(bool * aSingle)267 NS_IMETHODIMP nsTreeSelection::GetSingle(bool* aSingle) {
268   if (!mTree) {
269     return NS_ERROR_NULL_POINTER;
270   }
271 
272   *aSingle = mTree->AttrValueIs(kNameSpaceID_None, nsGkAtoms::seltype,
273                                 u"single"_ns, eCaseMatters);
274 
275   return NS_OK;
276 }
277 
IsSelected(int32_t aIndex,bool * aResult)278 NS_IMETHODIMP nsTreeSelection::IsSelected(int32_t aIndex, bool* aResult) {
279   if (mFirstRange)
280     *aResult = mFirstRange->Contains(aIndex);
281   else
282     *aResult = false;
283   return NS_OK;
284 }
285 
TimedSelect(int32_t aIndex,int32_t aMsec)286 NS_IMETHODIMP nsTreeSelection::TimedSelect(int32_t aIndex, int32_t aMsec) {
287   bool suppressSelect = mSuppressed;
288 
289   if (aMsec != -1) mSuppressed = true;
290 
291   nsresult rv = Select(aIndex);
292   if (NS_FAILED(rv)) return rv;
293 
294   if (aMsec != -1) {
295     mSuppressed = suppressSelect;
296     if (!mSuppressed) {
297       if (mSelectTimer) mSelectTimer->Cancel();
298 
299       nsIEventTarget* target =
300           mTree->OwnerDoc()->EventTargetFor(TaskCategory::Other);
301       NS_NewTimerWithFuncCallback(getter_AddRefs(mSelectTimer), SelectCallback,
302                                   this, aMsec, nsITimer::TYPE_ONE_SHOT,
303                                   "nsTreeSelection::SelectCallback", target);
304     }
305   }
306 
307   return NS_OK;
308 }
309 
Select(int32_t aIndex)310 NS_IMETHODIMP nsTreeSelection::Select(int32_t aIndex) {
311   mShiftSelectPivot = -1;
312 
313   nsresult rv = SetCurrentIndex(aIndex);
314   if (NS_FAILED(rv)) return rv;
315 
316   if (mFirstRange) {
317     bool alreadySelected = mFirstRange->Contains(aIndex);
318 
319     if (alreadySelected) {
320       int32_t count = mFirstRange->Count();
321       if (count > 1) {
322         // We need to deselect everything but our item.
323         mFirstRange->RemoveAllBut(aIndex);
324         FireOnSelectHandler();
325       }
326       return NS_OK;
327     } else {
328       // Clear out our selection.
329       mFirstRange->Invalidate();
330       delete mFirstRange;
331     }
332   }
333 
334   // Create our new selection.
335   mFirstRange = new nsTreeRange(this, aIndex);
336   if (!mFirstRange) return NS_ERROR_OUT_OF_MEMORY;
337 
338   mFirstRange->Invalidate();
339 
340   // Fire the select event
341   FireOnSelectHandler();
342   return NS_OK;
343 }
344 
ToggleSelect(int32_t aIndex)345 NS_IMETHODIMP nsTreeSelection::ToggleSelect(int32_t aIndex) {
346   // There are six cases that can occur on a ToggleSelect with our
347   // range code.
348   // (1) A new range should be made for a selection.
349   // (2) A single range is removed from the selection.
350   // (3) The item is added to an existing range.
351   // (4) The item is removed from an existing range.
352   // (5) The addition of the item causes two ranges to be merged.
353   // (6) The removal of the item causes two ranges to be split.
354   mShiftSelectPivot = -1;
355   nsresult rv = SetCurrentIndex(aIndex);
356   if (NS_FAILED(rv)) return rv;
357 
358   if (!mFirstRange)
359     Select(aIndex);
360   else {
361     if (!mFirstRange->Contains(aIndex)) {
362       bool single;
363       rv = GetSingle(&single);
364       if (NS_SUCCEEDED(rv) && !single) rv = mFirstRange->Add(aIndex);
365     } else
366       rv = mFirstRange->Remove(aIndex);
367     if (NS_SUCCEEDED(rv)) {
368       if (mTree) mTree->InvalidateRow(aIndex);
369 
370       FireOnSelectHandler();
371     }
372   }
373 
374   return rv;
375 }
376 
RangedSelect(int32_t aStartIndex,int32_t aEndIndex,bool aAugment)377 NS_IMETHODIMP nsTreeSelection::RangedSelect(int32_t aStartIndex,
378                                             int32_t aEndIndex, bool aAugment) {
379   bool single;
380   nsresult rv = GetSingle(&single);
381   if (NS_FAILED(rv)) return rv;
382 
383   if ((mFirstRange || (aStartIndex != aEndIndex)) && single) return NS_OK;
384 
385   if (!aAugment) {
386     // Clear our selection.
387     if (mFirstRange) {
388       mFirstRange->Invalidate();
389       delete mFirstRange;
390       mFirstRange = nullptr;
391     }
392   }
393 
394   if (aStartIndex == -1) {
395     if (mShiftSelectPivot != -1)
396       aStartIndex = mShiftSelectPivot;
397     else if (mCurrentIndex != -1)
398       aStartIndex = mCurrentIndex;
399     else
400       aStartIndex = aEndIndex;
401   }
402 
403   mShiftSelectPivot = aStartIndex;
404   rv = SetCurrentIndex(aEndIndex);
405   if (NS_FAILED(rv)) return rv;
406 
407   int32_t start = aStartIndex < aEndIndex ? aStartIndex : aEndIndex;
408   int32_t end = aStartIndex < aEndIndex ? aEndIndex : aStartIndex;
409 
410   if (aAugment && mFirstRange) {
411     // We need to remove all the items within our selected range from the
412     // selection, and then we insert our new range into the list.
413     nsresult rv = mFirstRange->RemoveRange(start, end);
414     if (NS_FAILED(rv)) return rv;
415   }
416 
417   nsTreeRange* range = new nsTreeRange(this, start, end);
418   if (!range) return NS_ERROR_OUT_OF_MEMORY;
419 
420   range->Invalidate();
421 
422   if (aAugment && mFirstRange)
423     mFirstRange->Insert(range);
424   else
425     mFirstRange = range;
426 
427   FireOnSelectHandler();
428 
429   return NS_OK;
430 }
431 
ClearRange(int32_t aStartIndex,int32_t aEndIndex)432 NS_IMETHODIMP nsTreeSelection::ClearRange(int32_t aStartIndex,
433                                           int32_t aEndIndex) {
434   nsresult rv = SetCurrentIndex(aEndIndex);
435   if (NS_FAILED(rv)) return rv;
436 
437   if (mFirstRange) {
438     int32_t start = aStartIndex < aEndIndex ? aStartIndex : aEndIndex;
439     int32_t end = aStartIndex < aEndIndex ? aEndIndex : aStartIndex;
440 
441     mFirstRange->RemoveRange(start, end);
442 
443     if (mTree) mTree->InvalidateRange(start, end);
444   }
445 
446   return NS_OK;
447 }
448 
ClearSelection()449 NS_IMETHODIMP nsTreeSelection::ClearSelection() {
450   if (mFirstRange) {
451     mFirstRange->Invalidate();
452     delete mFirstRange;
453     mFirstRange = nullptr;
454   }
455   mShiftSelectPivot = -1;
456 
457   FireOnSelectHandler();
458 
459   return NS_OK;
460 }
461 
SelectAll()462 NS_IMETHODIMP nsTreeSelection::SelectAll() {
463   if (!mTree) return NS_OK;
464 
465   nsCOMPtr<nsITreeView> view = mTree->GetView();
466   if (!view) return NS_OK;
467 
468   int32_t rowCount;
469   view->GetRowCount(&rowCount);
470   bool single;
471   nsresult rv = GetSingle(&single);
472   if (NS_FAILED(rv)) return rv;
473 
474   if (rowCount == 0 || (rowCount > 1 && single)) return NS_OK;
475 
476   mShiftSelectPivot = -1;
477 
478   // Invalidate not necessary when clearing selection, since
479   // we're going to invalidate the world on the SelectAll.
480   delete mFirstRange;
481 
482   mFirstRange = new nsTreeRange(this, 0, rowCount - 1);
483   mFirstRange->Invalidate();
484 
485   FireOnSelectHandler();
486 
487   return NS_OK;
488 }
489 
GetRangeCount(int32_t * aResult)490 NS_IMETHODIMP nsTreeSelection::GetRangeCount(int32_t* aResult) {
491   int32_t count = 0;
492   nsTreeRange* curr = mFirstRange;
493   while (curr) {
494     count++;
495     curr = curr->mNext;
496   }
497 
498   *aResult = count;
499   return NS_OK;
500 }
501 
GetRangeAt(int32_t aIndex,int32_t * aMin,int32_t * aMax)502 NS_IMETHODIMP nsTreeSelection::GetRangeAt(int32_t aIndex, int32_t* aMin,
503                                           int32_t* aMax) {
504   *aMin = *aMax = -1;
505   int32_t i = -1;
506   nsTreeRange* curr = mFirstRange;
507   while (curr) {
508     i++;
509     if (i == aIndex) {
510       *aMin = curr->mMin;
511       *aMax = curr->mMax;
512       break;
513     }
514     curr = curr->mNext;
515   }
516 
517   return NS_OK;
518 }
519 
GetCount(int32_t * count)520 NS_IMETHODIMP nsTreeSelection::GetCount(int32_t* count) {
521   if (mFirstRange)
522     *count = mFirstRange->Count();
523   else  // No range available, so there's no selected row.
524     *count = 0;
525 
526   return NS_OK;
527 }
528 
GetSelectEventsSuppressed(bool * aSelectEventsSuppressed)529 NS_IMETHODIMP nsTreeSelection::GetSelectEventsSuppressed(
530     bool* aSelectEventsSuppressed) {
531   *aSelectEventsSuppressed = mSuppressed;
532   return NS_OK;
533 }
534 
SetSelectEventsSuppressed(bool aSelectEventsSuppressed)535 NS_IMETHODIMP nsTreeSelection::SetSelectEventsSuppressed(
536     bool aSelectEventsSuppressed) {
537   mSuppressed = aSelectEventsSuppressed;
538   if (!mSuppressed) FireOnSelectHandler();
539   return NS_OK;
540 }
541 
GetCurrentIndex(int32_t * aCurrentIndex)542 NS_IMETHODIMP nsTreeSelection::GetCurrentIndex(int32_t* aCurrentIndex) {
543   *aCurrentIndex = mCurrentIndex;
544   return NS_OK;
545 }
546 
SetCurrentIndex(int32_t aIndex)547 NS_IMETHODIMP nsTreeSelection::SetCurrentIndex(int32_t aIndex) {
548   if (!mTree) {
549     return NS_ERROR_UNEXPECTED;
550   }
551   if (mCurrentIndex == aIndex) {
552     return NS_OK;
553   }
554   if (mCurrentIndex != -1 && mTree) mTree->InvalidateRow(mCurrentIndex);
555 
556   mCurrentIndex = aIndex;
557   if (!mTree) return NS_OK;
558 
559   if (aIndex != -1) mTree->InvalidateRow(aIndex);
560 
561   // Fire DOMMenuItemActive or DOMMenuItemInactive event for tree.
562   NS_ENSURE_STATE(mTree);
563 
564   constexpr auto DOMMenuItemActive = u"DOMMenuItemActive"_ns;
565   constexpr auto DOMMenuItemInactive = u"DOMMenuItemInactive"_ns;
566 
567   RefPtr<AsyncEventDispatcher> asyncDispatcher = new AsyncEventDispatcher(
568       mTree, (aIndex != -1 ? DOMMenuItemActive : DOMMenuItemInactive),
569       CanBubble::eYes, ChromeOnlyDispatch::eNo);
570   return asyncDispatcher->PostDOMEvent();
571 }
572 
573 #define ADD_NEW_RANGE(macro_range, macro_selection, macro_start, macro_end) \
574   {                                                                         \
575     int32_t start = macro_start;                                            \
576     int32_t end = macro_end;                                                \
577     if (start > end) {                                                      \
578       end = start;                                                          \
579     }                                                                       \
580     nsTreeRange* macro_new_range =                                          \
581         new nsTreeRange(macro_selection, start, end);                       \
582     if (macro_range)                                                        \
583       macro_range->Insert(macro_new_range);                                 \
584     else                                                                    \
585       macro_range = macro_new_range;                                        \
586   }
587 
588 NS_IMETHODIMP
AdjustSelection(int32_t aIndex,int32_t aCount)589 nsTreeSelection::AdjustSelection(int32_t aIndex, int32_t aCount) {
590   NS_ASSERTION(aCount != 0, "adjusting by zero");
591   if (!aCount) return NS_OK;
592 
593   // adjust mShiftSelectPivot, if necessary
594   if ((mShiftSelectPivot != 1) && (aIndex <= mShiftSelectPivot)) {
595     // if we are deleting and the delete includes the shift select pivot, reset
596     // it
597     if (aCount < 0 && (mShiftSelectPivot <= (aIndex - aCount - 1))) {
598       mShiftSelectPivot = -1;
599     } else {
600       mShiftSelectPivot += aCount;
601     }
602   }
603 
604   // adjust mCurrentIndex, if necessary
605   if ((mCurrentIndex != -1) && (aIndex <= mCurrentIndex)) {
606     // if we are deleting and the delete includes the current index, reset it
607     if (aCount < 0 && (mCurrentIndex <= (aIndex - aCount - 1))) {
608       mCurrentIndex = -1;
609     } else {
610       mCurrentIndex += aCount;
611     }
612   }
613 
614   // no selection, so nothing to do.
615   if (!mFirstRange) return NS_OK;
616 
617   bool selChanged = false;
618   nsTreeRange* oldFirstRange = mFirstRange;
619   nsTreeRange* curr = mFirstRange;
620   mFirstRange = nullptr;
621   while (curr) {
622     if (aCount > 0) {
623       // inserting
624       if (aIndex > curr->mMax) {
625         // adjustment happens after the range, so no change
626         ADD_NEW_RANGE(mFirstRange, this, curr->mMin, curr->mMax);
627       } else if (aIndex <= curr->mMin) {
628         // adjustment happens before the start of the range, so shift down
629         ADD_NEW_RANGE(mFirstRange, this, curr->mMin + aCount,
630                       curr->mMax + aCount);
631         selChanged = true;
632       } else {
633         // adjustment happen inside the range.
634         // break apart the range and create two ranges
635         ADD_NEW_RANGE(mFirstRange, this, curr->mMin, aIndex - 1);
636         ADD_NEW_RANGE(mFirstRange, this, aIndex + aCount, curr->mMax + aCount);
637         selChanged = true;
638       }
639     } else {
640       // deleting
641       if (aIndex > curr->mMax) {
642         // adjustment happens after the range, so no change
643         ADD_NEW_RANGE(mFirstRange, this, curr->mMin, curr->mMax);
644       } else {
645         // remember, aCount is negative
646         selChanged = true;
647         int32_t lastIndexOfAdjustment = aIndex - aCount - 1;
648         if (aIndex <= curr->mMin) {
649           if (lastIndexOfAdjustment < curr->mMin) {
650             // adjustment happens before the start of the range, so shift up
651             ADD_NEW_RANGE(mFirstRange, this, curr->mMin + aCount,
652                           curr->mMax + aCount);
653           } else if (lastIndexOfAdjustment >= curr->mMax) {
654             // adjustment contains the range.  remove the range by not adding it
655             // to the newRange
656           } else {
657             // adjustment starts before the range, and ends in the middle of it,
658             // so trim the range
659             ADD_NEW_RANGE(mFirstRange, this, aIndex, curr->mMax + aCount)
660           }
661         } else if (lastIndexOfAdjustment >= curr->mMax) {
662           // adjustment starts in the middle of the current range, and contains
663           // the end of the range, so trim the range
664           ADD_NEW_RANGE(mFirstRange, this, curr->mMin, aIndex - 1)
665         } else {
666           // range contains the adjustment, so shorten the range
667           ADD_NEW_RANGE(mFirstRange, this, curr->mMin, curr->mMax + aCount)
668         }
669       }
670     }
671     curr = curr->mNext;
672   }
673 
674   delete oldFirstRange;
675 
676   // Fire the select event
677   if (selChanged) FireOnSelectHandler();
678 
679   return NS_OK;
680 }
681 
682 NS_IMETHODIMP
InvalidateSelection()683 nsTreeSelection::InvalidateSelection() {
684   if (mFirstRange) mFirstRange->Invalidate();
685   return NS_OK;
686 }
687 
688 NS_IMETHODIMP
GetShiftSelectPivot(int32_t * aIndex)689 nsTreeSelection::GetShiftSelectPivot(int32_t* aIndex) {
690   *aIndex = mShiftSelectPivot;
691   return NS_OK;
692 }
693 
FireOnSelectHandler()694 nsresult nsTreeSelection::FireOnSelectHandler() {
695   if (mSuppressed || !mTree) return NS_OK;
696 
697   RefPtr<AsyncEventDispatcher> asyncDispatcher = new AsyncEventDispatcher(
698       mTree, u"select"_ns, CanBubble::eYes, ChromeOnlyDispatch::eNo);
699   asyncDispatcher->RunDOMEventWhenSafe();
700   return NS_OK;
701 }
702 
SelectCallback(nsITimer * aTimer,void * aClosure)703 void nsTreeSelection::SelectCallback(nsITimer* aTimer, void* aClosure) {
704   RefPtr<nsTreeSelection> self = static_cast<nsTreeSelection*>(aClosure);
705   if (self) {
706     self->FireOnSelectHandler();
707     aTimer->Cancel();
708     self->mSelectTimer = nullptr;
709   }
710 }
711 
712 ///////////////////////////////////////////////////////////////////////////////////
713 
NS_NewTreeSelection(XULTreeElement * aTree,nsITreeSelection ** aResult)714 nsresult NS_NewTreeSelection(XULTreeElement* aTree,
715                              nsITreeSelection** aResult) {
716   *aResult = new nsTreeSelection(aTree);
717   if (!*aResult) return NS_ERROR_OUT_OF_MEMORY;
718   NS_ADDREF(*aResult);
719   return NS_OK;
720 }
721