tor-browser

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

nsListControlFrame.cpp (37592B)


      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 "nsListControlFrame.h"
      8 
      9 #include <algorithm>
     10 
     11 #include "HTMLSelectEventListener.h"
     12 #include "mozilla/Attributes.h"
     13 #include "mozilla/EventStateManager.h"
     14 #include "mozilla/LookAndFeel.h"
     15 #include "mozilla/MouseEvents.h"
     16 #include "mozilla/Preferences.h"
     17 #include "mozilla/PresShell.h"
     18 #include "mozilla/StaticPrefs_browser.h"
     19 #include "mozilla/StaticPrefs_ui.h"
     20 #include "mozilla/TextEvents.h"
     21 #include "mozilla/dom/Event.h"
     22 #include "mozilla/dom/HTMLOptGroupElement.h"
     23 #include "mozilla/dom/HTMLOptionsCollection.h"
     24 #include "mozilla/dom/HTMLSelectElement.h"
     25 #include "mozilla/dom/MouseEvent.h"
     26 #include "mozilla/dom/MouseEventBinding.h"
     27 #include "nsCOMPtr.h"
     28 #include "nsCSSRendering.h"
     29 #include "nsComboboxControlFrame.h"
     30 #include "nsContentUtils.h"
     31 #include "nsDisplayList.h"
     32 #include "nsFontMetrics.h"
     33 #include "nsGkAtoms.h"
     34 #include "nsLayoutUtils.h"
     35 #include "nsUnicharUtils.h"
     36 #include "nscore.h"
     37 
     38 using namespace mozilla;
     39 using namespace mozilla::dom;
     40 
     41 //---------------------------------------------------------
     42 nsListControlFrame* NS_NewListControlFrame(PresShell* aPresShell,
     43                                           ComputedStyle* aStyle) {
     44  return new (aPresShell)
     45      nsListControlFrame(aStyle, aPresShell->GetPresContext());
     46 }
     47 
     48 NS_IMPL_FRAMEARENA_HELPERS(nsListControlFrame)
     49 
     50 nsListControlFrame::nsListControlFrame(ComputedStyle* aStyle,
     51                                       nsPresContext* aPresContext)
     52    : ScrollContainerFrame(aStyle, aPresContext, kClassID, false),
     53      mChangesSinceDragStart(false),
     54      mIsAllContentHere(false),
     55      mIsAllFramesHere(false),
     56      mHasBeenInitialized(false),
     57      mNeedToReset(true),
     58      mPostChildrenLoadedReset(false),
     59      mMightNeedSecondPass(false),
     60      mReflowWasInterrupted(false) {}
     61 
     62 nsListControlFrame::~nsListControlFrame() = default;
     63 
     64 Maybe<nscoord> nsListControlFrame::GetNaturalBaselineBOffset(
     65    WritingMode aWM, BaselineSharingGroup aBaselineGroup,
     66    BaselineExportContext) const {
     67  // Unlike scroll frames which we inherit from, we don't export a baseline.
     68  return Nothing{};
     69 }
     70 // for Bug 47302 (remove this comment later)
     71 void nsListControlFrame::Destroy(DestroyContext& aContext) {
     72  // get the receiver interface from the browser button's content node
     73  NS_ENSURE_TRUE_VOID(mContent);
     74 
     75  // Clear the frame pointer on our event listener, just in case the
     76  // event listener can outlive the frame.
     77 
     78  mEventListener->Detach();
     79  ScrollContainerFrame::Destroy(aContext);
     80 }
     81 
     82 HTMLOptionElement* nsListControlFrame::GetCurrentOption() const {
     83  return mEventListener->GetCurrentOption();
     84 }
     85 
     86 bool nsListControlFrame::IsFocused() const {
     87  return Select().State().HasState(ElementState::FOCUS);
     88 }
     89 
     90 void nsListControlFrame::InvalidateFocus() { InvalidateFrame(); }
     91 
     92 NS_QUERYFRAME_HEAD(nsListControlFrame)
     93  NS_QUERYFRAME_ENTRY(nsISelectControlFrame)
     94  NS_QUERYFRAME_ENTRY(nsListControlFrame)
     95 NS_QUERYFRAME_TAIL_INHERITING(ScrollContainerFrame)
     96 
     97 #ifdef ACCESSIBILITY
     98 a11y::AccType nsListControlFrame::AccessibleType() {
     99  return a11y::eHTMLSelectListType;
    100 }
    101 #endif
    102 
    103 // Return true if we found at least one <option> or non-empty <optgroup> label
    104 // that has a frame.  aResult will be the maximum BSize of those.
    105 static bool GetMaxRowBSize(nsIFrame* aContainer, WritingMode aWM,
    106                           nscoord* aResult) {
    107  bool found = false;
    108  for (nsIFrame* child : aContainer->PrincipalChildList()) {
    109    if (child->GetContent()->IsHTMLElement(nsGkAtoms::optgroup)) {
    110      // An optgroup; drill through any scroll frame and recurse.  |inner| might
    111      // be null here though if |inner| is an anonymous leaf frame of some sort.
    112      auto inner = child->GetContentInsertionFrame();
    113      if (inner && GetMaxRowBSize(inner, aWM, aResult)) {
    114        found = true;
    115      }
    116    } else {
    117      // an option or optgroup label
    118      bool isOptGroupLabel =
    119          child->Style()->IsPseudoElement() &&
    120          aContainer->GetContent()->IsHTMLElement(nsGkAtoms::optgroup);
    121      nscoord childBSize = child->BSize(aWM);
    122      // XXX bug 1499176: skip empty <optgroup> labels (zero bsize) for now
    123      if (!isOptGroupLabel || childBSize > nscoord(0)) {
    124        found = true;
    125        *aResult = std::max(childBSize, *aResult);
    126      }
    127    }
    128  }
    129  return found;
    130 }
    131 
    132 //-----------------------------------------------------------------
    133 // Main Reflow for ListBox/Dropdown
    134 //-----------------------------------------------------------------
    135 
    136 nscoord nsListControlFrame::CalcBSizeOfARow() {
    137  // Calculate the block size in our writing mode of a single row in the
    138  // listbox or dropdown list by using the tallest thing in the subtree,
    139  // since there may be option groups in addition to option elements,
    140  // either of which may be visible or invisible, may use different
    141  // fonts, etc.
    142  nscoord rowBSize(0);
    143  if (GetContainSizeAxes().mBContained ||
    144      !GetMaxRowBSize(GetContentInsertionFrame(), GetWritingMode(),
    145                      &rowBSize)) {
    146    // We don't have any <option>s or <optgroup> labels with a frame.
    147    // (Or we're size-contained in block axis, which has the same outcome for
    148    // our sizing.)
    149    float inflation = nsLayoutUtils::FontSizeInflationFor(this);
    150    rowBSize = CalcFallbackRowBSize(inflation);
    151  }
    152  return rowBSize;
    153 }
    154 
    155 nscoord nsListControlFrame::IntrinsicISize(const IntrinsicSizeInput& aInput,
    156                                           IntrinsicISizeType aType) {
    157  // Always add scrollbar inline sizes to the intrinsic isize of the
    158  // scrolled content. Combobox frames depend on this happening in the
    159  // dropdown, and standalone listboxes are overflow:scroll so they need
    160  // it too.
    161  WritingMode wm = GetWritingMode();
    162  nscoord result;
    163  if (Maybe<nscoord> containISize = ContainIntrinsicISize()) {
    164    result = *containISize;
    165  } else {
    166    result = GetScrolledFrame()->IntrinsicISize(aInput, aType);
    167  }
    168  LogicalMargin scrollbarSize(wm, GetDesiredScrollbarSizes());
    169  result = NSCoordSaturatingAdd(result, scrollbarSize.IStartEnd(wm));
    170  return result;
    171 }
    172 
    173 void nsListControlFrame::Reflow(nsPresContext* aPresContext,
    174                                ReflowOutput& aDesiredSize,
    175                                const ReflowInput& aReflowInput,
    176                                nsReflowStatus& aStatus) {
    177  MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
    178  NS_WARNING_ASSERTION(aReflowInput.ComputedISize() != NS_UNCONSTRAINEDSIZE,
    179                       "Must have a computed inline size");
    180 
    181  const bool hadPendingInterrupt = aPresContext->HasPendingInterrupt();
    182 
    183  SchedulePaint();
    184 
    185  // If all the content and frames are here
    186  // then initialize it before reflow
    187  if (mIsAllContentHere && !mHasBeenInitialized) {
    188    if (!mIsAllFramesHere) {
    189      CheckIfAllFramesHere();
    190    }
    191    if (mIsAllFramesHere && !mHasBeenInitialized) {
    192      mHasBeenInitialized = true;
    193    }
    194  }
    195 
    196  MarkInReflow();
    197  // Due to the fact that our intrinsic block size depends on the block
    198  // sizes of our kids, we end up having to do two-pass reflow, in
    199  // general -- the first pass to find the intrinsic block size and a
    200  // second pass to reflow the scrollframe at that block size (which
    201  // will size the scrollbars correctly, etc).
    202  //
    203  // Naturally, we want to avoid doing the second reflow as much as
    204  // possible. We can skip it in the following cases (in all of which the first
    205  // reflow is already happening at the right block size):
    206  bool autoBSize = (aReflowInput.ComputedBSize() == NS_UNCONSTRAINEDSIZE);
    207  Maybe<nscoord> containBSize = ContainIntrinsicBSize(NS_UNCONSTRAINEDSIZE);
    208  bool usingContainBSize =
    209      autoBSize && containBSize && *containBSize != NS_UNCONSTRAINEDSIZE;
    210 
    211  mMightNeedSecondPass = [&] {
    212    if (!autoBSize) {
    213      // We're reflowing with a constrained computed block size -- just use that
    214      // block size.
    215      return false;
    216    }
    217    if (!IsSubtreeDirty() && !aReflowInput.ShouldReflowAllKids()) {
    218      // We're not dirty and have no dirty kids and shouldn't be reflowing all
    219      // kids. In this case, our cached max block size of a child is not going
    220      // to change.
    221      return false;
    222    }
    223    if (usingContainBSize) {
    224      // We're size-contained in the block axis. In this case the size of a row
    225      // doesn't depend on our children (it's the "fallback" size).
    226      return false;
    227    }
    228    // We might need to do a second pass. If we do our first reflow using our
    229    // cached max block size of a child, then compute the new max block size,
    230    // and it's the same as the old one, we might still skip it (see the
    231    // IsScrollbarUpdateSuppressed() check).
    232    return true;
    233  }();
    234 
    235  ReflowInput state(aReflowInput);
    236  int32_t length = GetNumberOfRows();
    237 
    238  nscoord oldBSizeOfARow = BSizeOfARow();
    239 
    240  if (!HasAnyStateBits(NS_FRAME_FIRST_REFLOW) && autoBSize) {
    241    // When not doing an initial reflow, and when the block size is
    242    // auto, start off with our computed block size set to what we'd
    243    // expect our block size to be.
    244    nscoord computedBSize = CalcIntrinsicBSize(oldBSizeOfARow, length);
    245    computedBSize = state.ApplyMinMaxBSize(computedBSize);
    246    state.SetComputedBSize(computedBSize);
    247  }
    248 
    249  if (usingContainBSize) {
    250    state.SetComputedBSize(*containBSize);
    251  }
    252 
    253  ScrollContainerFrame::Reflow(aPresContext, aDesiredSize, state, aStatus);
    254 
    255  mBSizeOfARow = CalcBSizeOfARow();
    256 
    257  if (!mMightNeedSecondPass) {
    258    NS_ASSERTION(
    259        !autoBSize || usingContainBSize || BSizeOfARow() == oldBSizeOfARow,
    260        "How did our BSize of a row change if nothing was dirty?");
    261    NS_ASSERTION(!autoBSize || usingContainBSize ||
    262                     !HasAnyStateBits(NS_FRAME_FIRST_REFLOW),
    263                 "How do we not need a second pass during initial reflow at "
    264                 "auto BSize?");
    265    if (!autoBSize || usingContainBSize) {
    266      // Update our mNumDisplayRows based on our new row block size now
    267      // that we know it.  Note that if autoBSize and we landed in this
    268      // code then we already set mNumDisplayRows in CalcIntrinsicBSize.
    269      //  Also note that we can't use BSizeOfARow() here because that
    270      // just uses a cached value that we didn't compute.
    271      nscoord rowBSize = CalcBSizeOfARow();
    272      if (rowBSize == 0) {
    273        // Just pick something
    274        mNumDisplayRows = 1;
    275      } else {
    276        mNumDisplayRows = std::max(1, state.ComputedBSize() / rowBSize);
    277      }
    278    }
    279 
    280    return;
    281  }
    282 
    283  mMightNeedSecondPass = false;
    284 
    285  // Now see whether we need a second pass.  If we do, our
    286  // nsSelectsAreaFrame will have suppressed the scrollbar update.
    287  if (mBSizeOfARow == oldBSizeOfARow) {
    288    return;
    289  }
    290 
    291  // Gotta reflow again.
    292  // XXXbz We're just changing the block size here; do we need to dirty
    293  // ourselves or anything like that?  We might need to, per the letter
    294  // of the reflow protocol, but things seem to work fine without it...
    295  // Is that just an implementation detail of ScrollContainerFrame that
    296  // we're depending on?
    297  ScrollContainerFrame::DidReflow(aPresContext, &state);
    298 
    299  // Now compute the block size we want to have
    300  nscoord computedBSize = CalcIntrinsicBSize(BSizeOfARow(), length);
    301  computedBSize = state.ApplyMinMaxBSize(computedBSize);
    302  state.SetComputedBSize(computedBSize);
    303 
    304  // XXXbz to make the ascent really correct, we should add our
    305  // mComputedPadding.top to it (and subtract it from descent).  Need that
    306  // because ScrollContainerFrame just adds in the border....
    307  aStatus.Reset();
    308  ScrollContainerFrame::Reflow(aPresContext, aDesiredSize, state, aStatus);
    309 
    310  mReflowWasInterrupted |=
    311      !hadPendingInterrupt && aPresContext->HasPendingInterrupt();
    312 }
    313 
    314 bool nsListControlFrame::ShouldPropagateComputedBSizeToScrolledContent() const {
    315  return true;
    316 }
    317 
    318 //---------------------------------------------------------
    319 bool nsListControlFrame::ExtendedSelection(int32_t aStartIndex,
    320                                           int32_t aEndIndex, bool aClearAll) {
    321  return SetOptionsSelectedFromFrame(aStartIndex, aEndIndex, true, aClearAll);
    322 }
    323 
    324 //---------------------------------------------------------
    325 bool nsListControlFrame::SingleSelection(int32_t aClickedIndex,
    326                                         bool aDoToggle) {
    327 #ifdef ACCESSIBILITY
    328  nsCOMPtr<nsIContent> prevOption = mEventListener->GetCurrentOption();
    329 #endif
    330  bool wasChanged = false;
    331  // Get Current selection
    332  if (aDoToggle) {
    333    wasChanged = ToggleOptionSelectedFromFrame(aClickedIndex);
    334  } else {
    335    wasChanged =
    336        SetOptionsSelectedFromFrame(aClickedIndex, aClickedIndex, true, true);
    337  }
    338  AutoWeakFrame weakFrame(this);
    339  ScrollToIndex(aClickedIndex);
    340  if (!weakFrame.IsAlive()) {
    341    return wasChanged;
    342  }
    343 
    344  mStartSelectionIndex = aClickedIndex;
    345  mEndSelectionIndex = aClickedIndex;
    346  InvalidateFocus();
    347 
    348 #ifdef ACCESSIBILITY
    349  FireMenuItemActiveEvent(prevOption);
    350 #endif
    351 
    352  return wasChanged;
    353 }
    354 
    355 void nsListControlFrame::InitSelectionRange(int32_t aClickedIndex) {
    356  //
    357  // If nothing is selected, set the start selection depending on where
    358  // the user clicked and what the initial selection is:
    359  // - if the user clicked *before* selectedIndex, set the start index to
    360  //   the end of the first contiguous selection.
    361  // - if the user clicked *after* the end of the first contiguous
    362  //   selection, set the start index to selectedIndex.
    363  // - if the user clicked *within* the first contiguous selection, set the
    364  //   start index to selectedIndex.
    365  // The last two rules, of course, boil down to the same thing: if the user
    366  // clicked >= selectedIndex, return selectedIndex.
    367  //
    368  // This makes it so that shift click works properly when you first click
    369  // in a multiple select.
    370  //
    371  int32_t selectedIndex = GetSelectedIndex();
    372  if (selectedIndex >= 0) {
    373    // Get the end of the contiguous selection
    374    RefPtr<dom::HTMLOptionsCollection> options = GetOptions();
    375    NS_ASSERTION(options, "Collection of options is null!");
    376    uint32_t numOptions = options->Length();
    377    // Push i to one past the last selected index in the group.
    378    uint32_t i;
    379    for (i = selectedIndex + 1; i < numOptions; i++) {
    380      if (!options->ItemAsOption(i)->Selected()) {
    381        break;
    382      }
    383    }
    384 
    385    if (aClickedIndex < selectedIndex) {
    386      // User clicked before selection, so start selection at end of
    387      // contiguous selection
    388      mStartSelectionIndex = i - 1;
    389      mEndSelectionIndex = selectedIndex;
    390    } else {
    391      // User clicked after selection, so start selection at start of
    392      // contiguous selection
    393      mStartSelectionIndex = selectedIndex;
    394      mEndSelectionIndex = i - 1;
    395    }
    396  }
    397 }
    398 
    399 static uint32_t CountOptionsAndOptgroups(nsIFrame* aFrame) {
    400  uint32_t count = 0;
    401  for (nsIFrame* child : aFrame->PrincipalChildList()) {
    402    nsIContent* content = child->GetContent();
    403    if (content) {
    404      if (content->IsHTMLElement(nsGkAtoms::option)) {
    405        ++count;
    406      } else {
    407        RefPtr<HTMLOptGroupElement> optgroup =
    408            HTMLOptGroupElement::FromNode(content);
    409        if (optgroup) {
    410          nsAutoString label;
    411          optgroup->GetLabel(label);
    412          if (label.Length() > 0) {
    413            ++count;
    414          }
    415          count += CountOptionsAndOptgroups(child);
    416        }
    417      }
    418    }
    419  }
    420  return count;
    421 }
    422 
    423 uint32_t nsListControlFrame::GetNumberOfRows() {
    424  return ::CountOptionsAndOptgroups(GetContentInsertionFrame());
    425 }
    426 
    427 //---------------------------------------------------------
    428 bool nsListControlFrame::PerformSelection(int32_t aClickedIndex, bool aIsShift,
    429                                          bool aIsControl) {
    430  if (aClickedIndex == kNothingSelected) {
    431    // Ignore kNothingSelected.
    432    return false;
    433  }
    434  if (!GetMultiple()) {
    435    return SingleSelection(aClickedIndex, false);
    436  }
    437  bool wasChanged = false;
    438  if (aIsShift) {
    439    // Make sure shift+click actually does something expected when
    440    // the user has never clicked on the select
    441    if (mStartSelectionIndex == kNothingSelected) {
    442      InitSelectionRange(aClickedIndex);
    443    }
    444 
    445    // Get the range from beginning (low) to end (high)
    446    // Shift *always* works, even if the current option is disabled
    447    int32_t startIndex;
    448    int32_t endIndex;
    449    if (mStartSelectionIndex == kNothingSelected) {
    450      startIndex = aClickedIndex;
    451      endIndex = aClickedIndex;
    452    } else if (mStartSelectionIndex <= aClickedIndex) {
    453      startIndex = mStartSelectionIndex;
    454      endIndex = aClickedIndex;
    455    } else {
    456      startIndex = aClickedIndex;
    457      endIndex = mStartSelectionIndex;
    458    }
    459 
    460    // Clear only if control was not pressed
    461    wasChanged = ExtendedSelection(startIndex, endIndex, !aIsControl);
    462    AutoWeakFrame weakFrame(this);
    463    ScrollToIndex(aClickedIndex);
    464    if (!weakFrame.IsAlive()) {
    465      return wasChanged;
    466    }
    467 
    468    if (mStartSelectionIndex == kNothingSelected) {
    469      mStartSelectionIndex = aClickedIndex;
    470    }
    471 #ifdef ACCESSIBILITY
    472    nsCOMPtr<nsIContent> prevOption = GetCurrentOption();
    473 #endif
    474    mEndSelectionIndex = aClickedIndex;
    475    InvalidateFocus();
    476 
    477 #ifdef ACCESSIBILITY
    478    FireMenuItemActiveEvent(prevOption);
    479 #endif
    480  } else if (aIsControl) {
    481    wasChanged = SingleSelection(aClickedIndex, true);  // might destroy us
    482  } else {
    483    wasChanged = SingleSelection(aClickedIndex, false);  // might destroy us
    484  }
    485  return wasChanged;
    486 }
    487 
    488 //---------------------------------------------------------
    489 bool nsListControlFrame::HandleListSelection(dom::Event* aEvent,
    490                                             int32_t aClickedIndex) {
    491  MouseEvent* mouseEvent = aEvent->AsMouseEvent();
    492  bool isControl;
    493 #ifdef XP_MACOSX
    494  isControl = mouseEvent->MetaKey();
    495 #else
    496  isControl = mouseEvent->CtrlKey();
    497 #endif
    498  bool isShift = mouseEvent->ShiftKey();
    499  return PerformSelection(aClickedIndex, isShift,
    500                          isControl);  // might destroy us
    501 }
    502 
    503 //---------------------------------------------------------
    504 void nsListControlFrame::CaptureMouseEvents(bool aGrabMouseEvents) {
    505  if (aGrabMouseEvents) {
    506    PresShell::SetCapturingContent(mContent, CaptureFlags::IgnoreAllowedState);
    507  } else {
    508    nsIContent* capturingContent = PresShell::GetCapturingContent();
    509    if (capturingContent == mContent) {
    510      // only clear the capturing content if *we* are the ones doing the
    511      // capturing (or if the dropdown is hidden, in which case NO-ONE should
    512      // be capturing anything - it could be a scrollbar inside this listbox
    513      // which is actually grabbing
    514      // This shouldn't be necessary. We should simply ensure that events
    515      // targeting scrollbars are never visible to DOM consumers.
    516      PresShell::ReleaseCapturingContent();
    517    }
    518  }
    519 }
    520 
    521 //---------------------------------------------------------
    522 nsresult nsListControlFrame::HandleEvent(nsPresContext* aPresContext,
    523                                         WidgetGUIEvent* aEvent,
    524                                         nsEventStatus* aEventStatus) {
    525  NS_ENSURE_ARG_POINTER(aEventStatus);
    526 
    527  /*const char * desc[] = {"eMouseMove",
    528                          "NS_MOUSE_LEFT_BUTTON_UP",
    529                          "NS_MOUSE_LEFT_BUTTON_DOWN",
    530                          "<NA>","<NA>","<NA>","<NA>","<NA>","<NA>","<NA>",
    531                          "NS_MOUSE_MIDDLE_BUTTON_UP",
    532                          "NS_MOUSE_MIDDLE_BUTTON_DOWN",
    533                          "<NA>","<NA>","<NA>","<NA>","<NA>","<NA>","<NA>","<NA>",
    534                          "NS_MOUSE_RIGHT_BUTTON_UP",
    535                          "NS_MOUSE_RIGHT_BUTTON_DOWN",
    536                          "eMouseOver",
    537                          "eMouseOut",
    538                          "NS_MOUSE_LEFT_DOUBLECLICK",
    539                          "NS_MOUSE_MIDDLE_DOUBLECLICK",
    540                          "NS_MOUSE_RIGHT_DOUBLECLICK",
    541                          "NS_MOUSE_LEFT_CLICK",
    542                          "NS_MOUSE_MIDDLE_CLICK",
    543                          "NS_MOUSE_RIGHT_CLICK"};
    544  int inx = aEvent->mMessage - eMouseEventFirst;
    545  if (inx >= 0 && inx <= (NS_MOUSE_RIGHT_CLICK - eMouseEventFirst)) {
    546    printf("Mouse in ListFrame %s [%d]\n", desc[inx], aEvent->mMessage);
    547  } else {
    548    printf("Mouse in ListFrame <UNKNOWN> [%d]\n", aEvent->mMessage);
    549  }*/
    550 
    551  if (nsEventStatus_eConsumeNoDefault == *aEventStatus) {
    552    return NS_OK;
    553  }
    554 
    555  // disabled state affects how we're selected, but we don't want to go through
    556  // ScrollContainerFrame if we're disabled.
    557  if (IsContentDisabled()) {
    558    return nsIFrame::HandleEvent(aPresContext, aEvent, aEventStatus);
    559  }
    560 
    561  return ScrollContainerFrame::HandleEvent(aPresContext, aEvent, aEventStatus);
    562 }
    563 
    564 //---------------------------------------------------------
    565 void nsListControlFrame::SetInitialChildList(ChildListID aListID,
    566                                             nsFrameList&& aChildList) {
    567  if (aListID == FrameChildListID::Principal) {
    568    // First check to see if all the content has been added
    569    mIsAllContentHere = Select().IsDoneAddingChildren();
    570    if (!mIsAllContentHere) {
    571      mIsAllFramesHere = false;
    572      mHasBeenInitialized = false;
    573    }
    574  }
    575  ScrollContainerFrame::SetInitialChildList(aListID, std::move(aChildList));
    576 }
    577 
    578 bool nsListControlFrame::GetMultiple() const {
    579  return mContent->AsElement()->HasAttr(nsGkAtoms::multiple);
    580 }
    581 
    582 HTMLSelectElement& nsListControlFrame::Select() const {
    583  return *static_cast<HTMLSelectElement*>(GetContent());
    584 }
    585 
    586 //---------------------------------------------------------
    587 void nsListControlFrame::Init(nsIContent* aContent, nsContainerFrame* aParent,
    588                              nsIFrame* aPrevInFlow) {
    589  ScrollContainerFrame::Init(aContent, aParent, aPrevInFlow);
    590 
    591  // we shouldn't have to unregister this listener because when
    592  // our frame goes away all these content node go away as well
    593  // because our frame is the only one who references them.
    594  // we need to hook up our listeners before the editor is initialized
    595  mEventListener = new HTMLSelectEventListener(
    596      Select(), HTMLSelectEventListener::SelectType::Listbox);
    597 
    598  mStartSelectionIndex = kNothingSelected;
    599  mEndSelectionIndex = kNothingSelected;
    600 }
    601 
    602 dom::HTMLOptionsCollection* nsListControlFrame::GetOptions() const {
    603  return Select().Options();
    604 }
    605 
    606 dom::HTMLOptionElement* nsListControlFrame::GetOption(uint32_t aIndex) const {
    607  return Select().Item(aIndex);
    608 }
    609 
    610 NS_IMETHODIMP
    611 nsListControlFrame::OnOptionSelected(int32_t aIndex, bool aSelected) {
    612  if (aSelected) {
    613    ScrollToIndex(aIndex);
    614  }
    615  return NS_OK;
    616 }
    617 
    618 void nsListControlFrame::OnContentReset() { ResetList(true); }
    619 
    620 void nsListControlFrame::ResetList(bool aAllowScrolling) {
    621  // if all the frames aren't here
    622  // don't bother reseting
    623  if (!mIsAllFramesHere) {
    624    return;
    625  }
    626 
    627  if (aAllowScrolling) {
    628    mPostChildrenLoadedReset = true;
    629 
    630    // Scroll to the selected index
    631    int32_t indexToSelect = kNothingSelected;
    632 
    633    HTMLSelectElement* selectElement = HTMLSelectElement::FromNode(mContent);
    634    if (selectElement) {
    635      indexToSelect = selectElement->SelectedIndex();
    636      AutoWeakFrame weakFrame(this);
    637      ScrollToIndex(indexToSelect);
    638      if (!weakFrame.IsAlive()) {
    639        return;
    640      }
    641    }
    642  }
    643 
    644  mStartSelectionIndex = kNothingSelected;
    645  mEndSelectionIndex = kNothingSelected;
    646  InvalidateFocus();
    647  // Combobox will redisplay itself with the OnOptionSelected event
    648 }
    649 
    650 void nsListControlFrame::ElementStateChanged(ElementState aStates) {
    651  if (aStates.HasState(ElementState::FOCUS)) {
    652    InvalidateFocus();
    653  }
    654 }
    655 
    656 void nsListControlFrame::GetOptionText(uint32_t aIndex, nsAString& aStr) {
    657  aStr.Truncate();
    658  if (dom::HTMLOptionElement* optionElement = GetOption(aIndex)) {
    659    optionElement->GetRenderedLabel(aStr);
    660  }
    661 }
    662 
    663 int32_t nsListControlFrame::GetSelectedIndex() {
    664  dom::HTMLSelectElement* select =
    665      dom::HTMLSelectElement::FromNodeOrNull(mContent);
    666  return select->SelectedIndex();
    667 }
    668 
    669 uint32_t nsListControlFrame::GetNumberOfOptions() {
    670  dom::HTMLOptionsCollection* options = GetOptions();
    671  if (!options) {
    672    return 0;
    673  }
    674 
    675  return options->Length();
    676 }
    677 
    678 //----------------------------------------------------------------------
    679 // nsISelectControlFrame
    680 //----------------------------------------------------------------------
    681 bool nsListControlFrame::CheckIfAllFramesHere() {
    682  // XXX Need to find a fail proof way to determine that
    683  // all the frames are there
    684  mIsAllFramesHere = true;
    685 
    686  // now make sure we have a frame each piece of content
    687 
    688  return mIsAllFramesHere;
    689 }
    690 
    691 NS_IMETHODIMP
    692 nsListControlFrame::DoneAddingChildren(bool aIsDone) {
    693  mIsAllContentHere = aIsDone;
    694  if (mIsAllContentHere) {
    695    // Here we check to see if all the frames have been created
    696    // for all the content.
    697    // If so, then we can initialize;
    698    if (!mIsAllFramesHere) {
    699      // if all the frames are now present we can initialize
    700      if (CheckIfAllFramesHere()) {
    701        mHasBeenInitialized = true;
    702        ResetList(true);
    703      }
    704    }
    705  }
    706  return NS_OK;
    707 }
    708 
    709 NS_IMETHODIMP
    710 nsListControlFrame::AddOption(int32_t aIndex) {
    711  if (!mIsAllContentHere) {
    712    mIsAllContentHere = Select().IsDoneAddingChildren();
    713    if (!mIsAllContentHere) {
    714      mIsAllFramesHere = false;
    715      mHasBeenInitialized = false;
    716    } else {
    717      mIsAllFramesHere =
    718          (aIndex == static_cast<int32_t>(GetNumberOfOptions() - 1));
    719    }
    720  }
    721 
    722  // Make sure we scroll to the selected option as needed
    723  mNeedToReset = true;
    724 
    725  if (!mHasBeenInitialized) {
    726    return NS_OK;
    727  }
    728 
    729  mPostChildrenLoadedReset = mIsAllContentHere;
    730  return NS_OK;
    731 }
    732 
    733 static int32_t DecrementAndClamp(int32_t aSelectionIndex, int32_t aLength) {
    734  return aLength == 0 ? nsListControlFrame::kNothingSelected
    735                      : std::max(0, aSelectionIndex - 1);
    736 }
    737 
    738 NS_IMETHODIMP
    739 nsListControlFrame::RemoveOption(int32_t aIndex) {
    740  MOZ_ASSERT(aIndex >= 0, "negative <option> index");
    741 
    742  // Need to reset if we're a dropdown
    743  if (mStartSelectionIndex != kNothingSelected) {
    744    NS_ASSERTION(mEndSelectionIndex != kNothingSelected, "");
    745    int32_t numOptions = GetNumberOfOptions();
    746    // NOTE: numOptions is the new number of options whereas aIndex is the
    747    // unadjusted index of the removed option (hence the <= below).
    748    NS_ASSERTION(aIndex <= numOptions, "out-of-bounds <option> index");
    749 
    750    int32_t forward = mEndSelectionIndex - mStartSelectionIndex;
    751    int32_t* low = forward >= 0 ? &mStartSelectionIndex : &mEndSelectionIndex;
    752    int32_t* high = forward >= 0 ? &mEndSelectionIndex : &mStartSelectionIndex;
    753    if (aIndex < *low) {
    754      *low = ::DecrementAndClamp(*low, numOptions);
    755    }
    756    if (aIndex <= *high) {
    757      *high = ::DecrementAndClamp(*high, numOptions);
    758    }
    759    if (forward == 0) {
    760      *low = *high;
    761    }
    762  } else {
    763    NS_ASSERTION(mEndSelectionIndex == kNothingSelected, "");
    764  }
    765 
    766  InvalidateFocus();
    767  return NS_OK;
    768 }
    769 
    770 //---------------------------------------------------------
    771 // Set the option selected in the DOM.  This method is named
    772 // as it is because it indicates that the frame is the source
    773 // of this event rather than the receiver.
    774 bool nsListControlFrame::SetOptionsSelectedFromFrame(int32_t aStartIndex,
    775                                                     int32_t aEndIndex,
    776                                                     bool aValue,
    777                                                     bool aClearAll) {
    778  using OptionFlag = HTMLSelectElement::OptionFlag;
    779  RefPtr<HTMLSelectElement> selectElement =
    780      HTMLSelectElement::FromNode(mContent);
    781 
    782  HTMLSelectElement::OptionFlags mask = OptionFlag::Notify;
    783  if (aValue) {
    784    mask += OptionFlag::IsSelected;
    785  }
    786  if (aClearAll) {
    787    mask += OptionFlag::ClearAll;
    788  }
    789 
    790  return selectElement->SetOptionsSelectedByIndex(aStartIndex, aEndIndex, mask);
    791 }
    792 
    793 bool nsListControlFrame::ToggleOptionSelectedFromFrame(int32_t aIndex) {
    794  RefPtr<HTMLOptionElement> option = GetOption(static_cast<uint32_t>(aIndex));
    795  NS_ENSURE_TRUE(option, false);
    796 
    797  RefPtr<HTMLSelectElement> selectElement =
    798      HTMLSelectElement::FromNode(mContent);
    799 
    800  HTMLSelectElement::OptionFlags mask = HTMLSelectElement::OptionFlag::Notify;
    801  if (!option->Selected()) {
    802    mask += HTMLSelectElement::OptionFlag::IsSelected;
    803  }
    804 
    805  return selectElement->SetOptionsSelectedByIndex(aIndex, aIndex, mask);
    806 }
    807 
    808 // Dispatch event and such
    809 bool nsListControlFrame::UpdateSelection() {
    810  if (mIsAllFramesHere) {
    811    // if it's a combobox, display the new text. Note that after
    812    // FireOnInputAndOnChange we might be dead, as that can run script.
    813    AutoWeakFrame weakFrame(this);
    814    if (mIsAllContentHere) {
    815      RefPtr listener = mEventListener;
    816      listener->FireOnInputAndOnChange();
    817    }
    818    return weakFrame.IsAlive();
    819  }
    820  return true;
    821 }
    822 
    823 NS_IMETHODIMP_(void)
    824 nsListControlFrame::OnSetSelectedIndex(int32_t aOldIndex, int32_t aNewIndex) {
    825 #ifdef ACCESSIBILITY
    826  nsCOMPtr<nsIContent> prevOption = GetCurrentOption();
    827 #endif
    828 
    829  AutoWeakFrame weakFrame(this);
    830  ScrollToIndex(aNewIndex);
    831  if (!weakFrame.IsAlive()) {
    832    return;
    833  }
    834  mStartSelectionIndex = aNewIndex;
    835  mEndSelectionIndex = aNewIndex;
    836  InvalidateFocus();
    837 
    838 #ifdef ACCESSIBILITY
    839  if (aOldIndex != aNewIndex) {
    840    FireMenuItemActiveEvent(prevOption);
    841  }
    842 #endif
    843 }
    844 
    845 //----------------------------------------------------------------------
    846 // End nsISelectControlFrame
    847 //----------------------------------------------------------------------
    848 
    849 class AsyncReset final : public Runnable {
    850 public:
    851  AsyncReset(nsListControlFrame* aFrame, bool aScroll)
    852      : Runnable("AsyncReset"), mFrame(aFrame), mScroll(aScroll) {}
    853 
    854  MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD Run() override {
    855    if (mFrame.IsAlive()) {
    856      static_cast<nsListControlFrame*>(mFrame.GetFrame())->ResetList(mScroll);
    857    }
    858    return NS_OK;
    859  }
    860 
    861 private:
    862  WeakFrame mFrame;
    863  bool mScroll;
    864 };
    865 
    866 bool nsListControlFrame::ReflowFinished() {
    867  if (mNeedToReset && !mReflowWasInterrupted) {
    868    mNeedToReset = false;
    869    // Suppress scrolling to the selected element if we restored scroll
    870    // history state AND the list contents have not changed since we loaded
    871    // all the children AND nothing else forced us to scroll by calling
    872    // ResetList(true). The latter two conditions are folded into
    873    // mPostChildrenLoadedReset.
    874    //
    875    // The idea is that we want scroll history restoration to trump ResetList
    876    // scrolling to the selected element, when the ResetList was probably only
    877    // caused by content loading normally.
    878    const bool scroll = !DidHistoryRestore() || mPostChildrenLoadedReset;
    879    nsContentUtils::AddScriptRunner(new AsyncReset(this, scroll));
    880  }
    881  mReflowWasInterrupted = false;
    882  return ScrollContainerFrame::ReflowFinished();
    883 }
    884 
    885 #ifdef DEBUG_FRAME_DUMP
    886 nsresult nsListControlFrame::GetFrameName(nsAString& aResult) const {
    887  return MakeFrameName(u"ListControl"_ns, aResult);
    888 }
    889 #endif
    890 
    891 nscoord nsListControlFrame::GetBSizeOfARow() { return BSizeOfARow(); }
    892 
    893 bool nsListControlFrame::IsOptionInteractivelySelectable(int32_t aIndex) const {
    894  auto& select = Select();
    895  if (HTMLOptionElement* item = select.Item(aIndex)) {
    896    return IsOptionInteractivelySelectable(&select, item);
    897  }
    898  return false;
    899 }
    900 
    901 bool nsListControlFrame::IsOptionInteractivelySelectable(
    902    HTMLSelectElement* aSelect, HTMLOptionElement* aOption) {
    903  return !aSelect->IsOptionDisabled(aOption) && aOption->GetPrimaryFrame();
    904 }
    905 
    906 nscoord nsListControlFrame::CalcFallbackRowBSize(float aFontSizeInflation) {
    907  RefPtr<nsFontMetrics> fontMet =
    908      nsLayoutUtils::GetFontMetricsForFrame(this, aFontSizeInflation);
    909  return fontMet->MaxHeight();
    910 }
    911 
    912 nscoord nsListControlFrame::CalcIntrinsicBSize(nscoord aBSizeOfARow,
    913                                               int32_t aNumberOfOptions) {
    914  if (Style()->StyleUIReset()->mFieldSizing == StyleFieldSizing::Content) {
    915    int32_t length = GetNumberOfRows();
    916    return length * aBSizeOfARow;
    917  }
    918 
    919  mNumDisplayRows = Select().Size();
    920  if (mNumDisplayRows < 1) {
    921    mNumDisplayRows = 4;
    922  }
    923  return mNumDisplayRows * aBSizeOfARow;
    924 }
    925 
    926 #ifdef ACCESSIBILITY
    927 void nsListControlFrame::FireMenuItemActiveEvent(nsIContent* aPreviousOption) {
    928  if (!IsFocused()) {
    929    return;
    930  }
    931 
    932  nsIContent* optionContent = GetCurrentOption();
    933  if (aPreviousOption == optionContent) {
    934    // No change
    935    return;
    936  }
    937 
    938  if (aPreviousOption) {
    939    FireDOMEvent(u"DOMMenuItemInactive"_ns, aPreviousOption);
    940  }
    941 
    942  if (optionContent) {
    943    FireDOMEvent(u"DOMMenuItemActive"_ns, optionContent);
    944  }
    945 }
    946 #endif
    947 
    948 nsresult nsListControlFrame::GetIndexFromDOMEvent(dom::Event* aMouseEvent,
    949                                                  int32_t& aCurIndex) {
    950  if (PresShell::GetCapturingContent() != mContent) {
    951    // If we're not capturing, then ignore movement in the border
    952    nsPoint pt =
    953        nsLayoutUtils::GetDOMEventCoordinatesRelativeTo(aMouseEvent, this);
    954    nsRect borderInnerEdge = GetScrollPortRect();
    955    if (!borderInnerEdge.Contains(pt)) {
    956      return NS_ERROR_FAILURE;
    957    }
    958  }
    959 
    960  RefPtr<dom::HTMLOptionElement> option;
    961  for (nsCOMPtr<nsIContent> content =
    962           PresContext()->EventStateManager()->GetEventTargetContent(nullptr);
    963       content && !option; content = content->GetParent()) {
    964    option = dom::HTMLOptionElement::FromNode(content);
    965  }
    966 
    967  if (option) {
    968    aCurIndex = option->Index();
    969    MOZ_ASSERT(aCurIndex >= 0);
    970    return NS_OK;
    971  }
    972 
    973  return NS_ERROR_FAILURE;
    974 }
    975 
    976 nsresult nsListControlFrame::HandleLeftButtonMouseDown(
    977    dom::Event* aMouseEvent) {
    978  int32_t selectedIndex;
    979  if (NS_SUCCEEDED(GetIndexFromDOMEvent(aMouseEvent, selectedIndex))) {
    980    // Handle Like List
    981    CaptureMouseEvents(true);
    982    AutoWeakFrame weakFrame(this);
    983    bool change =
    984        HandleListSelection(aMouseEvent, selectedIndex);  // might destroy us
    985    if (!weakFrame.IsAlive()) {
    986      return NS_OK;
    987    }
    988    mChangesSinceDragStart = change;
    989  }
    990  return NS_OK;
    991 }
    992 
    993 nsresult nsListControlFrame::HandleLeftButtonMouseUp(dom::Event* aMouseEvent) {
    994  if (!StyleVisibility()->IsVisible()) {
    995    return NS_OK;
    996  }
    997  // Notify
    998  if (mChangesSinceDragStart) {
    999    // reset this so that future MouseUps without a prior MouseDown
   1000    // won't fire onchange
   1001    mChangesSinceDragStart = false;
   1002    RefPtr listener = mEventListener;
   1003    listener->FireOnInputAndOnChange();
   1004    // Note that `this` may be dead now, as the above call runs script.
   1005  }
   1006  return NS_OK;
   1007 }
   1008 
   1009 nsresult nsListControlFrame::DragMove(dom::Event* aMouseEvent) {
   1010  NS_ASSERTION(aMouseEvent, "aMouseEvent is null.");
   1011 
   1012  int32_t selectedIndex;
   1013  if (NS_SUCCEEDED(GetIndexFromDOMEvent(aMouseEvent, selectedIndex))) {
   1014    // Don't waste cycles if we already dragged over this item
   1015    if (selectedIndex == mEndSelectionIndex) {
   1016      return NS_OK;
   1017    }
   1018    MouseEvent* mouseEvent = aMouseEvent->AsMouseEvent();
   1019    NS_ASSERTION(mouseEvent, "aMouseEvent is not a MouseEvent!");
   1020    bool isControl;
   1021 #ifdef XP_MACOSX
   1022    isControl = mouseEvent->MetaKey();
   1023 #else
   1024    isControl = mouseEvent->CtrlKey();
   1025 #endif
   1026    AutoWeakFrame weakFrame(this);
   1027    // Turn SHIFT on when you are dragging, unless control is on.
   1028    bool wasChanged = PerformSelection(selectedIndex, !isControl, isControl);
   1029    if (!weakFrame.IsAlive()) {
   1030      return NS_OK;
   1031    }
   1032    mChangesSinceDragStart = mChangesSinceDragStart || wasChanged;
   1033  }
   1034  return NS_OK;
   1035 }
   1036 
   1037 //----------------------------------------------------------------------
   1038 // Scroll helpers.
   1039 //----------------------------------------------------------------------
   1040 void nsListControlFrame::ScrollToIndex(int32_t aIndex) {
   1041  if (aIndex < 0) {
   1042    // XXX shouldn't we just do nothing if we're asked to scroll to
   1043    // kNothingSelected?
   1044    ScrollTo(nsPoint(0, 0), ScrollMode::Instant);
   1045  } else {
   1046    RefPtr<dom::HTMLOptionElement> option =
   1047        GetOption(AssertedCast<uint32_t>(aIndex));
   1048    if (option) {
   1049      ScrollToFrame(*option);
   1050    }
   1051  }
   1052 }
   1053 
   1054 void nsListControlFrame::ScrollToFrame(dom::HTMLOptionElement& aOptElement) {
   1055  // otherwise we find the content's frame and scroll to it
   1056  if (nsIFrame* childFrame = aOptElement.GetPrimaryFrame()) {
   1057    RefPtr<mozilla::PresShell> presShell = PresShell();
   1058    presShell->ScrollFrameIntoView(childFrame, Nothing(), ScrollAxis(),
   1059                                   ScrollAxis(),
   1060                                   ScrollFlags::ScrollOverflowHidden |
   1061                                       ScrollFlags::ScrollFirstAncestorOnly);
   1062  }
   1063 }
   1064 
   1065 void nsListControlFrame::UpdateSelectionAfterKeyEvent(
   1066    int32_t aNewIndex, uint32_t aCharCode, bool aIsShift, bool aIsControlOrMeta,
   1067    bool aIsControlSelectMode) {
   1068  // If you hold control, but not shift, no key will actually do anything
   1069  // except space.
   1070  AutoWeakFrame weakFrame(this);
   1071  bool wasChanged = false;
   1072  if (aIsControlOrMeta && !aIsShift && aCharCode != ' ') {
   1073 #ifdef ACCESSIBILITY
   1074    nsCOMPtr<nsIContent> prevOption = GetCurrentOption();
   1075 #endif
   1076    mStartSelectionIndex = aNewIndex;
   1077    mEndSelectionIndex = aNewIndex;
   1078    InvalidateFocus();
   1079    ScrollToIndex(aNewIndex);
   1080    if (!weakFrame.IsAlive()) {
   1081      return;
   1082    }
   1083 
   1084 #ifdef ACCESSIBILITY
   1085    FireMenuItemActiveEvent(prevOption);
   1086 #endif
   1087  } else if (aIsControlSelectMode && aCharCode == ' ') {
   1088    wasChanged = SingleSelection(aNewIndex, true);
   1089  } else {
   1090    wasChanged = PerformSelection(aNewIndex, aIsShift, aIsControlOrMeta);
   1091  }
   1092  if (wasChanged && weakFrame.IsAlive()) {
   1093    // dispatch event, update combobox, etc.
   1094    UpdateSelection();
   1095  }
   1096 }