tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

nsTreeSelection.cpp (21356B)


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