layout/forms/nsListControlFrame.cpp

Tue, 06 Jan 2015 21:39:09 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Tue, 06 Jan 2015 21:39:09 +0100
branch
TOR_BUG_9701
changeset 8
97036ab72558
permissions
-rw-r--r--

Conditionally force memory storage according to privacy.thirdparty.isolate;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.

     1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
     2 /* This Source Code Form is subject to the terms of the Mozilla Public
     3  * License, v. 2.0. If a copy of the MPL was not distributed with this
     4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     6 #include "nscore.h"
     7 #include "nsCOMPtr.h"
     8 #include "nsUnicharUtils.h"
     9 #include "nsListControlFrame.h"
    10 #include "nsFormControlFrame.h" // for COMPARE macro
    11 #include "nsGkAtoms.h"
    12 #include "nsIDOMHTMLSelectElement.h"
    13 #include "nsIDOMHTMLOptionElement.h"
    14 #include "nsComboboxControlFrame.h"
    15 #include "nsIDOMHTMLOptGroupElement.h"
    16 #include "nsIPresShell.h"
    17 #include "nsIDOMMouseEvent.h"
    18 #include "nsIXULRuntime.h"
    19 #include "nsFontMetrics.h"
    20 #include "nsIScrollableFrame.h"
    21 #include "nsCSSRendering.h"
    22 #include "nsIDOMEventListener.h"
    23 #include "nsLayoutUtils.h"
    24 #include "nsDisplayList.h"
    25 #include "nsContentUtils.h"
    26 #include "mozilla/Attributes.h"
    27 #include "mozilla/dom/HTMLOptionsCollection.h"
    28 #include "mozilla/dom/HTMLSelectElement.h"
    29 #include "mozilla/EventStateManager.h"
    30 #include "mozilla/EventStates.h"
    31 #include "mozilla/LookAndFeel.h"
    32 #include "mozilla/MouseEvents.h"
    33 #include "mozilla/Preferences.h"
    34 #include "mozilla/TextEvents.h"
    35 #include <algorithm>
    37 using namespace mozilla;
    39 // Constants
    40 const uint32_t kMaxDropDownRows         = 20; // This matches the setting for 4.x browsers
    41 const int32_t kNothingSelected          = -1;
    43 // Static members
    44 nsListControlFrame * nsListControlFrame::mFocused = nullptr;
    45 nsString * nsListControlFrame::sIncrementalString = nullptr;
    47 // Using for incremental typing navigation
    48 #define INCREMENTAL_SEARCH_KEYPRESS_TIME 1000
    49 // XXX, kyle.yuan@sun.com, there are 4 definitions for the same purpose:
    50 //  nsMenuPopupFrame.h, nsListControlFrame.cpp, listbox.xml, tree.xml
    51 //  need to find a good place to put them together.
    52 //  if someone changes one, please also change the other.
    54 DOMTimeStamp nsListControlFrame::gLastKeyTime = 0;
    56 /******************************************************************************
    57  * nsListEventListener
    58  * This class is responsible for propagating events to the nsListControlFrame.
    59  * Frames are not refcounted so they can't be used as event listeners.
    60  *****************************************************************************/
    62 class nsListEventListener MOZ_FINAL : public nsIDOMEventListener
    63 {
    64 public:
    65   nsListEventListener(nsListControlFrame *aFrame)
    66     : mFrame(aFrame) { }
    68   void SetFrame(nsListControlFrame *aFrame) { mFrame = aFrame; }
    70   NS_DECL_ISUPPORTS
    71   NS_DECL_NSIDOMEVENTLISTENER
    73 private:
    74   nsListControlFrame  *mFrame;
    75 };
    77 //---------------------------------------------------------
    78 nsIFrame*
    79 NS_NewListControlFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
    80 {
    81   nsListControlFrame* it =
    82     new (aPresShell) nsListControlFrame(aPresShell, aPresShell->GetDocument(), aContext);
    84   it->AddStateBits(NS_FRAME_INDEPENDENT_SELECTION);
    86   return it;
    87 }
    89 NS_IMPL_FRAMEARENA_HELPERS(nsListControlFrame)
    91 //---------------------------------------------------------
    92 nsListControlFrame::nsListControlFrame(
    93   nsIPresShell* aShell, nsIDocument* aDocument, nsStyleContext* aContext)
    94   : nsHTMLScrollFrame(aShell, aContext, false),
    95     mMightNeedSecondPass(false),
    96     mHasPendingInterruptAtStartOfReflow(false),
    97     mDropdownCanGrow(false),
    98     mLastDropdownComputedHeight(NS_UNCONSTRAINEDSIZE)
    99 {
   100   mComboboxFrame      = nullptr;
   101   mChangesSinceDragStart = false;
   102   mButtonDown         = false;
   104   mIsAllContentHere   = false;
   105   mIsAllFramesHere    = false;
   106   mHasBeenInitialized = false;
   107   mNeedToReset        = true;
   108   mPostChildrenLoadedReset = false;
   110   mControlSelectMode           = false;
   111 }
   113 //---------------------------------------------------------
   114 nsListControlFrame::~nsListControlFrame()
   115 {
   116   mComboboxFrame = nullptr;
   117 }
   119 // for Bug 47302 (remove this comment later)
   120 void
   121 nsListControlFrame::DestroyFrom(nsIFrame* aDestructRoot)
   122 {
   123   // get the receiver interface from the browser button's content node
   124   ENSURE_TRUE(mContent);
   126   // Clear the frame pointer on our event listener, just in case the
   127   // event listener can outlive the frame.
   129   mEventListener->SetFrame(nullptr);
   131   mContent->RemoveSystemEventListener(NS_LITERAL_STRING("keydown"),
   132                                       mEventListener, false);
   133   mContent->RemoveSystemEventListener(NS_LITERAL_STRING("keypress"),
   134                                       mEventListener, false);
   135   mContent->RemoveSystemEventListener(NS_LITERAL_STRING("mousedown"),
   136                                       mEventListener, false);
   137   mContent->RemoveSystemEventListener(NS_LITERAL_STRING("mouseup"),
   138                                       mEventListener, false);
   139   mContent->RemoveSystemEventListener(NS_LITERAL_STRING("mousemove"),
   140                                       mEventListener, false);
   142   nsFormControlFrame::RegUnRegAccessKey(static_cast<nsIFrame*>(this), false);
   143   nsHTMLScrollFrame::DestroyFrom(aDestructRoot);
   144 }
   146 void
   147 nsListControlFrame::BuildDisplayList(nsDisplayListBuilder*   aBuilder,
   148                                      const nsRect&           aDirtyRect,
   149                                      const nsDisplayListSet& aLists)
   150 {
   151   // We allow visibility:hidden <select>s to contain visible options.
   153   // Don't allow painting of list controls when painting is suppressed.
   154   // XXX why do we need this here? we should never reach this. Maybe
   155   // because these can have widgets? Hmm
   156   if (aBuilder->IsBackgroundOnly())
   157     return;
   159   DO_GLOBAL_REFLOW_COUNT_DSP("nsListControlFrame");
   161   if (IsInDropDownMode()) {
   162     NS_ASSERTION(NS_GET_A(mLastDropdownBackstopColor) == 255,
   163                  "need an opaque backstop color");
   164     // XXX Because we have an opaque widget and we get called to paint with
   165     // this frame as the root of a stacking context we need make sure to draw
   166     // some opaque color over the whole widget. (Bug 511323)
   167     aLists.BorderBackground()->AppendNewToBottom(
   168       new (aBuilder) nsDisplaySolidColor(aBuilder,
   169         this, nsRect(aBuilder->ToReferenceFrame(this), GetSize()),
   170         mLastDropdownBackstopColor));
   171   }
   173   nsHTMLScrollFrame::BuildDisplayList(aBuilder, aDirtyRect, aLists);
   174 }
   176 /**
   177  * This is called by the SelectsAreaFrame, which is the same
   178  * as the frame returned by GetOptionsContainer. It's the frame which is
   179  * scrolled by us.
   180  * @param aPt the offset of this frame, relative to the rendering reference
   181  * frame
   182  */
   183 void nsListControlFrame::PaintFocus(nsRenderingContext& aRC, nsPoint aPt)
   184 {
   185   if (mFocused != this) return;
   187   nsPresContext* presContext = PresContext();
   189   nsIFrame* containerFrame = GetOptionsContainer();
   190   if (!containerFrame) return;
   192   nsIFrame* childframe = nullptr;
   193   nsCOMPtr<nsIContent> focusedContent = GetCurrentOption();
   194   if (focusedContent) {
   195     childframe = focusedContent->GetPrimaryFrame();
   196   }
   198   nsRect fRect;
   199   if (childframe) {
   200     // get the child rect
   201     fRect = childframe->GetRect();
   202     // get it into our coordinates
   203     fRect.MoveBy(childframe->GetParent()->GetOffsetTo(this));
   204   } else {
   205     float inflation = nsLayoutUtils::FontSizeInflationFor(this);
   206     fRect.x = fRect.y = 0;
   207     fRect.width = GetScrollPortRect().width;
   208     fRect.height = CalcFallbackRowHeight(inflation);
   209     fRect.MoveBy(containerFrame->GetOffsetTo(this));
   210   }
   211   fRect += aPt;
   213   bool lastItemIsSelected = false;
   214   if (focusedContent) {
   215     nsCOMPtr<nsIDOMHTMLOptionElement> domOpt =
   216       do_QueryInterface(focusedContent);
   217     if (domOpt) {
   218       domOpt->GetSelected(&lastItemIsSelected);
   219     }
   220   }
   222   // set up back stop colors and then ask L&F service for the real colors
   223   nscolor color =
   224     LookAndFeel::GetColor(lastItemIsSelected ?
   225                             LookAndFeel::eColorID_WidgetSelectForeground :
   226                             LookAndFeel::eColorID_WidgetSelectBackground);
   228   nsCSSRendering::PaintFocus(presContext, aRC, fRect, color);
   229 }
   231 void
   232 nsListControlFrame::InvalidateFocus()
   233 {
   234   if (mFocused != this)
   235     return;
   237   nsIFrame* containerFrame = GetOptionsContainer();
   238   if (containerFrame) {
   239     containerFrame->InvalidateFrame();
   240   }
   241 }
   243 NS_QUERYFRAME_HEAD(nsListControlFrame)
   244   NS_QUERYFRAME_ENTRY(nsIFormControlFrame)
   245   NS_QUERYFRAME_ENTRY(nsIListControlFrame)
   246   NS_QUERYFRAME_ENTRY(nsISelectControlFrame)
   247 NS_QUERYFRAME_TAIL_INHERITING(nsHTMLScrollFrame)
   249 #ifdef ACCESSIBILITY
   250 a11y::AccType
   251 nsListControlFrame::AccessibleType()
   252 {
   253   return a11y::eHTMLSelectListType;
   254 }
   255 #endif
   257 static nscoord
   258 GetMaxOptionHeight(nsIFrame* aContainer)
   259 {
   260   nscoord result = 0;
   261   for (nsIFrame* option = aContainer->GetFirstPrincipalChild();
   262        option; option = option->GetNextSibling()) {
   263     nscoord optionHeight;
   264     if (nsCOMPtr<nsIDOMHTMLOptGroupElement>
   265         (do_QueryInterface(option->GetContent()))) {
   266       // an optgroup
   267       optionHeight = GetMaxOptionHeight(option);
   268     } else {
   269       // an option
   270       optionHeight = option->GetSize().height;
   271     }
   272     if (result < optionHeight)
   273       result = optionHeight;
   274   }
   275   return result;
   276 }
   278 //-----------------------------------------------------------------
   279 // Main Reflow for ListBox/Dropdown
   280 //-----------------------------------------------------------------
   282 nscoord
   283 nsListControlFrame::CalcHeightOfARow()
   284 {
   285   // Calculate the height of a single row in the listbox or dropdown list by
   286   // using the tallest thing in the subtree, since there may be option groups
   287   // in addition to option elements, either of which may be visible or
   288   // invisible, may use different fonts, etc.
   289   int32_t heightOfARow = GetMaxOptionHeight(GetOptionsContainer());
   291   // Check to see if we have zero items (and optimize by checking
   292   // heightOfARow first)
   293   if (heightOfARow == 0 && GetNumberOfOptions() == 0) {
   294     float inflation = nsLayoutUtils::FontSizeInflationFor(this);
   295     heightOfARow = CalcFallbackRowHeight(inflation);
   296   }
   298   return heightOfARow;
   299 }
   301 nscoord
   302 nsListControlFrame::GetPrefWidth(nsRenderingContext *aRenderingContext)
   303 {
   304   nscoord result;
   305   DISPLAY_PREF_WIDTH(this, result);
   307   // Always add scrollbar widths to the pref-width of the scrolled
   308   // content. Combobox frames depend on this happening in the dropdown,
   309   // and standalone listboxes are overflow:scroll so they need it too.
   310   result = GetScrolledFrame()->GetPrefWidth(aRenderingContext);
   311   result = NSCoordSaturatingAdd(result,
   312           GetDesiredScrollbarSizes(PresContext(), aRenderingContext).LeftRight());
   314   return result;
   315 }
   317 nscoord
   318 nsListControlFrame::GetMinWidth(nsRenderingContext *aRenderingContext)
   319 {
   320   nscoord result;
   321   DISPLAY_MIN_WIDTH(this, result);
   323   // Always add scrollbar widths to the min-width of the scrolled
   324   // content. Combobox frames depend on this happening in the dropdown,
   325   // and standalone listboxes are overflow:scroll so they need it too.
   326   result = GetScrolledFrame()->GetMinWidth(aRenderingContext);
   327   result += GetDesiredScrollbarSizes(PresContext(), aRenderingContext).LeftRight();
   329   return result;
   330 }
   332 nsresult 
   333 nsListControlFrame::Reflow(nsPresContext*           aPresContext, 
   334                            nsHTMLReflowMetrics&     aDesiredSize,
   335                            const nsHTMLReflowState& aReflowState, 
   336                            nsReflowStatus&          aStatus)
   337 {
   338   NS_PRECONDITION(aReflowState.ComputedWidth() != NS_UNCONSTRAINEDSIZE,
   339                   "Must have a computed width");
   341   SchedulePaint();
   343   mHasPendingInterruptAtStartOfReflow = aPresContext->HasPendingInterrupt();
   345   // If all the content and frames are here 
   346   // then initialize it before reflow
   347   if (mIsAllContentHere && !mHasBeenInitialized) {
   348     if (false == mIsAllFramesHere) {
   349       CheckIfAllFramesHere();
   350     }
   351     if (mIsAllFramesHere && !mHasBeenInitialized) {
   352       mHasBeenInitialized = true;
   353     }
   354   }
   356   if (GetStateBits() & NS_FRAME_FIRST_REFLOW) {
   357     nsFormControlFrame::RegUnRegAccessKey(this, true);
   358   }
   360   if (IsInDropDownMode()) {
   361     return ReflowAsDropdown(aPresContext, aDesiredSize, aReflowState, aStatus);
   362   }
   364   /*
   365    * Due to the fact that our intrinsic height depends on the heights of our
   366    * kids, we end up having to do two-pass reflow, in general -- the first pass
   367    * to find the intrinsic height and a second pass to reflow the scrollframe
   368    * at that height (which will size the scrollbars correctly, etc).
   369    *
   370    * Naturaly, we want to avoid doing the second reflow as much as possible.
   371    * We can skip it in the following cases (in all of which the first reflow is
   372    * already happening at the right height):
   373    *
   374    * - We're reflowing with a constrained computed height -- just use that
   375    *   height.
   376    * - We're not dirty and have no dirty kids and shouldn't be reflowing all
   377    *   kids.  In this case, our cached max height of a child is not going to
   378    *   change.
   379    * - We do our first reflow using our cached max height of a child, then
   380    *   compute the new max height and it's the same as the old one.
   381    */
   383   bool autoHeight = (aReflowState.ComputedHeight() == NS_UNCONSTRAINEDSIZE);
   385   mMightNeedSecondPass = autoHeight &&
   386     (NS_SUBTREE_DIRTY(this) || aReflowState.ShouldReflowAllKids());
   388   nsHTMLReflowState state(aReflowState);
   389   int32_t length = GetNumberOfRows();
   391   nscoord oldHeightOfARow = HeightOfARow();
   393   if (!(GetStateBits() & NS_FRAME_FIRST_REFLOW) && autoHeight) {
   394     // When not doing an initial reflow, and when the height is auto, start off
   395     // with our computed height set to what we'd expect our height to be.
   396     nscoord computedHeight = CalcIntrinsicHeight(oldHeightOfARow, length);
   397     computedHeight = state.ApplyMinMaxHeight(computedHeight);
   398     state.SetComputedHeight(computedHeight);
   399   }
   401   nsresult rv = nsHTMLScrollFrame::Reflow(aPresContext, aDesiredSize,
   402                                           state, aStatus);
   403   NS_ENSURE_SUCCESS(rv, rv);
   405   if (!mMightNeedSecondPass) {
   406     NS_ASSERTION(!autoHeight || HeightOfARow() == oldHeightOfARow,
   407                  "How did our height of a row change if nothing was dirty?");
   408     NS_ASSERTION(!autoHeight ||
   409                  !(GetStateBits() & NS_FRAME_FIRST_REFLOW),
   410                  "How do we not need a second pass during initial reflow at "
   411                  "auto height?");
   412     NS_ASSERTION(!IsScrollbarUpdateSuppressed(),
   413                  "Shouldn't be suppressing if we don't need a second pass!");
   414     if (!autoHeight) {
   415       // Update our mNumDisplayRows based on our new row height now that we
   416       // know it.  Note that if autoHeight and we landed in this code then we
   417       // already set mNumDisplayRows in CalcIntrinsicHeight.  Also note that we
   418       // can't use HeightOfARow() here because that just uses a cached value
   419       // that we didn't compute.
   420       nscoord rowHeight = CalcHeightOfARow();
   421       if (rowHeight == 0) {
   422         // Just pick something
   423         mNumDisplayRows = 1;
   424       } else {
   425         mNumDisplayRows = std::max(1, state.ComputedHeight() / rowHeight);
   426       }
   427     }
   429     return rv;
   430   }
   432   mMightNeedSecondPass = false;
   434   // Now see whether we need a second pass.  If we do, our nsSelectsAreaFrame
   435   // will have suppressed the scrollbar update.
   436   if (!IsScrollbarUpdateSuppressed()) {
   437     // All done.  No need to do more reflow.
   438     NS_ASSERTION(!IsScrollbarUpdateSuppressed(),
   439                  "Shouldn't be suppressing if the height of a row has not "
   440                  "changed!");
   441     return rv;
   442   }
   444   SetSuppressScrollbarUpdate(false);
   446   // Gotta reflow again.
   447   // XXXbz We're just changing the height here; do we need to dirty ourselves
   448   // or anything like that?  We might need to, per the letter of the reflow
   449   // protocol, but things seem to work fine without it...  Is that just an
   450   // implementation detail of nsHTMLScrollFrame that we're depending on?
   451   nsHTMLScrollFrame::DidReflow(aPresContext, &state,
   452                                nsDidReflowStatus::FINISHED);
   454   // Now compute the height we want to have
   455   nscoord computedHeight = CalcIntrinsicHeight(HeightOfARow(), length); 
   456   computedHeight = state.ApplyMinMaxHeight(computedHeight);
   457   state.SetComputedHeight(computedHeight);
   459   nsHTMLScrollFrame::WillReflow(aPresContext);
   461   // XXXbz to make the ascent really correct, we should add our
   462   // mComputedPadding.top to it (and subtract it from descent).  Need that
   463   // because nsGfxScrollFrame just adds in the border....
   464   return nsHTMLScrollFrame::Reflow(aPresContext, aDesiredSize, state, aStatus);
   465 }
   467 nsresult
   468 nsListControlFrame::ReflowAsDropdown(nsPresContext*           aPresContext, 
   469                                      nsHTMLReflowMetrics&     aDesiredSize,
   470                                      const nsHTMLReflowState& aReflowState, 
   471                                      nsReflowStatus&          aStatus)
   472 {
   473   NS_PRECONDITION(aReflowState.ComputedHeight() == NS_UNCONSTRAINEDSIZE,
   474                   "We should not have a computed height here!");
   476   mMightNeedSecondPass = NS_SUBTREE_DIRTY(this) ||
   477     aReflowState.ShouldReflowAllKids();
   479 #ifdef DEBUG
   480   nscoord oldHeightOfARow = HeightOfARow();
   481   nscoord oldVisibleHeight = (GetStateBits() & NS_FRAME_FIRST_REFLOW) ?
   482     NS_UNCONSTRAINEDSIZE : GetScrolledFrame()->GetSize().height;
   483 #endif
   485   nsHTMLReflowState state(aReflowState);
   487   if (!(GetStateBits() & NS_FRAME_FIRST_REFLOW)) {
   488     // When not doing an initial reflow, and when the height is auto, start off
   489     // with our computed height set to what we'd expect our height to be.
   490     // Note: At this point, mLastDropdownComputedHeight can be
   491     // NS_UNCONSTRAINEDSIZE in cases when last time we didn't have to constrain
   492     // the height.  That's fine; just do the same thing as last time.
   493     state.SetComputedHeight(mLastDropdownComputedHeight);
   494   }
   496   nsresult rv = nsHTMLScrollFrame::Reflow(aPresContext, aDesiredSize,
   497                                           state, aStatus);
   498   NS_ENSURE_SUCCESS(rv, rv);
   500   if (!mMightNeedSecondPass) {
   501     NS_ASSERTION(oldVisibleHeight == GetScrolledFrame()->GetSize().height,
   502                  "How did our kid's height change if nothing was dirty?");
   503     NS_ASSERTION(HeightOfARow() == oldHeightOfARow,
   504                  "How did our height of a row change if nothing was dirty?");
   505     NS_ASSERTION(!IsScrollbarUpdateSuppressed(),
   506                  "Shouldn't be suppressing if we don't need a second pass!");
   507     NS_ASSERTION(!(GetStateBits() & NS_FRAME_FIRST_REFLOW),
   508                  "How can we avoid a second pass during first reflow?");
   509     return rv;
   510   }
   512   mMightNeedSecondPass = false;
   514   // Now see whether we need a second pass.  If we do, our nsSelectsAreaFrame
   515   // will have suppressed the scrollbar update.
   516   if (!IsScrollbarUpdateSuppressed()) {
   517     // All done.  No need to do more reflow.
   518     NS_ASSERTION(!(GetStateBits() & NS_FRAME_FIRST_REFLOW),
   519                  "How can we avoid a second pass during first reflow?");
   520     return rv;
   521   }
   523   SetSuppressScrollbarUpdate(false);
   525   nscoord visibleHeight = GetScrolledFrame()->GetSize().height;
   526   nscoord heightOfARow = HeightOfARow();
   528   // Gotta reflow again.
   529   // XXXbz We're just changing the height here; do we need to dirty ourselves
   530   // or anything like that?  We might need to, per the letter of the reflow
   531   // protocol, but things seem to work fine without it...  Is that just an
   532   // implementation detail of nsHTMLScrollFrame that we're depending on?
   533   nsHTMLScrollFrame::DidReflow(aPresContext, &state,
   534                                nsDidReflowStatus::FINISHED);
   536   // Now compute the height we want to have.
   537   // Note: no need to apply min/max constraints, since we have no such
   538   // rules applied to the combobox dropdown.
   540   mDropdownCanGrow = false;
   541   if (visibleHeight <= 0 || heightOfARow <= 0) {
   542     // Looks like we have no options.  Just size us to a single row height.
   543     state.SetComputedHeight(heightOfARow);
   544     mNumDisplayRows = 1;
   545   } else {
   546     nsComboboxControlFrame* combobox = static_cast<nsComboboxControlFrame*>(mComboboxFrame);
   547     nsPoint translation;
   548     nscoord above, below;
   549     combobox->GetAvailableDropdownSpace(&above, &below, &translation);
   550     if (above <= 0 && below <= 0) {
   551       state.SetComputedHeight(heightOfARow);
   552       mNumDisplayRows = 1;
   553       mDropdownCanGrow = GetNumberOfRows() > 1;
   554     } else {
   555       nscoord bp = aReflowState.ComputedPhysicalBorderPadding().TopBottom();
   556       nscoord availableHeight = std::max(above, below) - bp;
   557       nscoord newHeight;
   558       uint32_t rows;
   559       if (visibleHeight <= availableHeight) {
   560         // The dropdown fits in the available height.
   561         rows = GetNumberOfRows();
   562         mNumDisplayRows = clamped<uint32_t>(rows, 1, kMaxDropDownRows);
   563         if (mNumDisplayRows == rows) {
   564           newHeight = visibleHeight;  // use the exact height
   565         } else {
   566           newHeight = mNumDisplayRows * heightOfARow; // approximate
   567         }
   568       } else {
   569         rows = availableHeight / heightOfARow;
   570         mNumDisplayRows = clamped<uint32_t>(rows, 1, kMaxDropDownRows);
   571         newHeight = mNumDisplayRows * heightOfARow; // approximate
   572       }
   573       state.SetComputedHeight(newHeight);
   574       mDropdownCanGrow = visibleHeight - newHeight >= heightOfARow &&
   575                          mNumDisplayRows != kMaxDropDownRows;
   576     }
   577   }
   579   mLastDropdownComputedHeight = state.ComputedHeight();
   581   nsHTMLScrollFrame::WillReflow(aPresContext);
   582   return nsHTMLScrollFrame::Reflow(aPresContext, aDesiredSize, state, aStatus);
   583 }
   585 ScrollbarStyles
   586 nsListControlFrame::GetScrollbarStyles() const
   587 {
   588   // We can't express this in the style system yet; when we can, this can go away
   589   // and GetScrollbarStyles can be devirtualized
   590   int32_t verticalStyle = IsInDropDownMode() ? NS_STYLE_OVERFLOW_AUTO
   591     : NS_STYLE_OVERFLOW_SCROLL;
   592   return ScrollbarStyles(NS_STYLE_OVERFLOW_HIDDEN, verticalStyle);
   593 }
   595 bool
   596 nsListControlFrame::ShouldPropagateComputedHeightToScrolledContent() const
   597 {
   598   return !IsInDropDownMode();
   599 }
   601 //---------------------------------------------------------
   602 nsIFrame*
   603 nsListControlFrame::GetContentInsertionFrame() {
   604   return GetOptionsContainer()->GetContentInsertionFrame();
   605 }
   607 //---------------------------------------------------------
   608 bool
   609 nsListControlFrame::ExtendedSelection(int32_t aStartIndex,
   610                                       int32_t aEndIndex,
   611                                       bool aClearAll)
   612 {
   613   return SetOptionsSelectedFromFrame(aStartIndex, aEndIndex,
   614                                      true, aClearAll);
   615 }
   617 //---------------------------------------------------------
   618 bool
   619 nsListControlFrame::SingleSelection(int32_t aClickedIndex, bool aDoToggle)
   620 {
   621   if (mComboboxFrame) {
   622     mComboboxFrame->UpdateRecentIndex(GetSelectedIndex());
   623   }
   625   bool wasChanged = false;
   626   // Get Current selection
   627   if (aDoToggle) {
   628     wasChanged = ToggleOptionSelectedFromFrame(aClickedIndex);
   629   } else {
   630     wasChanged = SetOptionsSelectedFromFrame(aClickedIndex, aClickedIndex,
   631                                 true, true);
   632   }
   633   nsWeakFrame weakFrame(this);
   634   ScrollToIndex(aClickedIndex);
   635   if (!weakFrame.IsAlive()) {
   636     return wasChanged;
   637   }
   639 #ifdef ACCESSIBILITY
   640   bool isCurrentOptionChanged = mEndSelectionIndex != aClickedIndex;
   641 #endif
   642   mStartSelectionIndex = aClickedIndex;
   643   mEndSelectionIndex = aClickedIndex;
   644   InvalidateFocus();
   646 #ifdef ACCESSIBILITY
   647   if (isCurrentOptionChanged) {
   648     FireMenuItemActiveEvent();
   649   }
   650 #endif
   652   return wasChanged;
   653 }
   655 void
   656 nsListControlFrame::InitSelectionRange(int32_t aClickedIndex)
   657 {
   658   //
   659   // If nothing is selected, set the start selection depending on where
   660   // the user clicked and what the initial selection is:
   661   // - if the user clicked *before* selectedIndex, set the start index to
   662   //   the end of the first contiguous selection.
   663   // - if the user clicked *after* the end of the first contiguous
   664   //   selection, set the start index to selectedIndex.
   665   // - if the user clicked *within* the first contiguous selection, set the
   666   //   start index to selectedIndex.
   667   // The last two rules, of course, boil down to the same thing: if the user
   668   // clicked >= selectedIndex, return selectedIndex.
   669   //
   670   // This makes it so that shift click works properly when you first click
   671   // in a multiple select.
   672   //
   673   int32_t selectedIndex = GetSelectedIndex();
   674   if (selectedIndex >= 0) {
   675     // Get the end of the contiguous selection
   676     nsRefPtr<dom::HTMLOptionsCollection> options = GetOptions();
   677     NS_ASSERTION(options, "Collection of options is null!");
   678     uint32_t numOptions = options->Length();
   679     // Push i to one past the last selected index in the group.
   680     uint32_t i;
   681     for (i = selectedIndex + 1; i < numOptions; i++) {
   682       if (!options->ItemAsOption(i)->Selected()) {
   683         break;
   684       }
   685     }
   687     if (aClickedIndex < selectedIndex) {
   688       // User clicked before selection, so start selection at end of
   689       // contiguous selection
   690       mStartSelectionIndex = i-1;
   691       mEndSelectionIndex = selectedIndex;
   692     } else {
   693       // User clicked after selection, so start selection at start of
   694       // contiguous selection
   695       mStartSelectionIndex = selectedIndex;
   696       mEndSelectionIndex = i-1;
   697     }
   698   }
   699 }
   701 static uint32_t
   702 CountOptionsAndOptgroups(nsIFrame* aFrame)
   703 {
   704   uint32_t count = 0;
   705   nsFrameList::Enumerator e(aFrame->PrincipalChildList());
   706   for (; !e.AtEnd(); e.Next()) {
   707     nsIFrame* child = e.get();
   708     nsIContent* content = child->GetContent();
   709     if (content) {
   710       if (content->IsHTML(nsGkAtoms::option)) {
   711         ++count;
   712       } else {
   713         nsCOMPtr<nsIDOMHTMLOptGroupElement> optgroup = do_QueryInterface(content);
   714         if (optgroup) {
   715           nsAutoString label;
   716           optgroup->GetLabel(label);
   717           if (label.Length() > 0) {
   718             ++count;
   719           }
   720           count += CountOptionsAndOptgroups(child);
   721         }
   722       }
   723     }
   724   }
   725   return count;
   726 }
   728 uint32_t
   729 nsListControlFrame::GetNumberOfRows()
   730 {
   731   return ::CountOptionsAndOptgroups(GetContentInsertionFrame());
   732 }
   734 //---------------------------------------------------------
   735 bool
   736 nsListControlFrame::PerformSelection(int32_t aClickedIndex,
   737                                      bool aIsShift,
   738                                      bool aIsControl)
   739 {
   740   bool wasChanged = false;
   742   if (aClickedIndex == kNothingSelected) {
   743   }
   744   else if (GetMultiple()) {
   745     if (aIsShift) {
   746       // Make sure shift+click actually does something expected when
   747       // the user has never clicked on the select
   748       if (mStartSelectionIndex == kNothingSelected) {
   749         InitSelectionRange(aClickedIndex);
   750       }
   752       // Get the range from beginning (low) to end (high)
   753       // Shift *always* works, even if the current option is disabled
   754       int32_t startIndex;
   755       int32_t endIndex;
   756       if (mStartSelectionIndex == kNothingSelected) {
   757         startIndex = aClickedIndex;
   758         endIndex   = aClickedIndex;
   759       } else if (mStartSelectionIndex <= aClickedIndex) {
   760         startIndex = mStartSelectionIndex;
   761         endIndex   = aClickedIndex;
   762       } else {
   763         startIndex = aClickedIndex;
   764         endIndex   = mStartSelectionIndex;
   765       }
   767       // Clear only if control was not pressed
   768       wasChanged = ExtendedSelection(startIndex, endIndex, !aIsControl);
   769       nsWeakFrame weakFrame(this);
   770       ScrollToIndex(aClickedIndex);
   771       if (!weakFrame.IsAlive()) {
   772         return wasChanged;
   773       }
   775       if (mStartSelectionIndex == kNothingSelected) {
   776         mStartSelectionIndex = aClickedIndex;
   777       }
   778 #ifdef ACCESSIBILITY
   779       bool isCurrentOptionChanged = mEndSelectionIndex != aClickedIndex;
   780 #endif
   781       mEndSelectionIndex = aClickedIndex;
   782       InvalidateFocus();
   784 #ifdef ACCESSIBILITY
   785       if (isCurrentOptionChanged) {
   786         FireMenuItemActiveEvent();
   787       }
   788 #endif
   789     } else if (aIsControl) {
   790       wasChanged = SingleSelection(aClickedIndex, true); // might destroy us
   791     } else {
   792       wasChanged = SingleSelection(aClickedIndex, false); // might destroy us
   793     }
   794   } else {
   795     wasChanged = SingleSelection(aClickedIndex, false); // might destroy us
   796   }
   798   return wasChanged;
   799 }
   801 //---------------------------------------------------------
   802 bool
   803 nsListControlFrame::HandleListSelection(nsIDOMEvent* aEvent,
   804                                         int32_t aClickedIndex)
   805 {
   806   nsCOMPtr<nsIDOMMouseEvent> mouseEvent = do_QueryInterface(aEvent);
   807   bool isShift;
   808   bool isControl;
   809 #ifdef XP_MACOSX
   810   mouseEvent->GetMetaKey(&isControl);
   811 #else
   812   mouseEvent->GetCtrlKey(&isControl);
   813 #endif
   814   mouseEvent->GetShiftKey(&isShift);
   815   return PerformSelection(aClickedIndex, isShift, isControl); // might destroy us
   816 }
   818 //---------------------------------------------------------
   819 void
   820 nsListControlFrame::CaptureMouseEvents(bool aGrabMouseEvents)
   821 {
   822   // Currently cocoa widgets use a native popup widget which tracks clicks synchronously,
   823   // so we never want to do mouse capturing. Note that we only bail if the list
   824   // is in drop-down mode, and the caller is requesting capture (we let release capture
   825   // requests go through to ensure that we can release capture requested via other
   826   // code paths, if any exist).
   827   if (aGrabMouseEvents && IsInDropDownMode() && nsComboboxControlFrame::ToolkitHasNativePopup())
   828     return;
   830   if (aGrabMouseEvents) {
   831     nsIPresShell::SetCapturingContent(mContent, CAPTURE_IGNOREALLOWED);
   832   } else {
   833     nsIContent* capturingContent = nsIPresShell::GetCapturingContent();
   835     bool dropDownIsHidden = false;
   836     if (IsInDropDownMode()) {
   837       dropDownIsHidden = !mComboboxFrame->IsDroppedDown();
   838     }
   839     if (capturingContent == mContent || dropDownIsHidden) {
   840       // only clear the capturing content if *we* are the ones doing the
   841       // capturing (or if the dropdown is hidden, in which case NO-ONE should
   842       // be capturing anything - it could be a scrollbar inside this listbox
   843       // which is actually grabbing
   844       // This shouldn't be necessary. We should simply ensure that events targeting
   845       // scrollbars are never visible to DOM consumers.
   846       nsIPresShell::SetCapturingContent(nullptr, 0);
   847     }
   848   }
   849 }
   851 //---------------------------------------------------------
   852 nsresult 
   853 nsListControlFrame::HandleEvent(nsPresContext* aPresContext,
   854                                 WidgetGUIEvent* aEvent,
   855                                 nsEventStatus* aEventStatus)
   856 {
   857   NS_ENSURE_ARG_POINTER(aEventStatus);
   859   /*const char * desc[] = {"NS_MOUSE_MOVE", 
   860                           "NS_MOUSE_LEFT_BUTTON_UP",
   861                           "NS_MOUSE_LEFT_BUTTON_DOWN",
   862                           "<NA>","<NA>","<NA>","<NA>","<NA>","<NA>","<NA>",
   863                           "NS_MOUSE_MIDDLE_BUTTON_UP",
   864                           "NS_MOUSE_MIDDLE_BUTTON_DOWN",
   865                           "<NA>","<NA>","<NA>","<NA>","<NA>","<NA>","<NA>","<NA>",
   866                           "NS_MOUSE_RIGHT_BUTTON_UP",
   867                           "NS_MOUSE_RIGHT_BUTTON_DOWN",
   868                           "NS_MOUSE_ENTER_SYNTH",
   869                           "NS_MOUSE_EXIT_SYNTH",
   870                           "NS_MOUSE_LEFT_DOUBLECLICK",
   871                           "NS_MOUSE_MIDDLE_DOUBLECLICK",
   872                           "NS_MOUSE_RIGHT_DOUBLECLICK",
   873                           "NS_MOUSE_LEFT_CLICK",
   874                           "NS_MOUSE_MIDDLE_CLICK",
   875                           "NS_MOUSE_RIGHT_CLICK"};
   876   int inx = aEvent->message-NS_MOUSE_MESSAGE_START;
   877   if (inx >= 0 && inx <= (NS_MOUSE_RIGHT_CLICK-NS_MOUSE_MESSAGE_START)) {
   878     printf("Mouse in ListFrame %s [%d]\n", desc[inx], aEvent->message);
   879   } else {
   880     printf("Mouse in ListFrame <UNKNOWN> [%d]\n", aEvent->message);
   881   }*/
   883   if (nsEventStatus_eConsumeNoDefault == *aEventStatus)
   884     return NS_OK;
   886   // do we have style that affects how we are selected?
   887   // do we have user-input style?
   888   const nsStyleUserInterface* uiStyle = StyleUserInterface();
   889   if (uiStyle->mUserInput == NS_STYLE_USER_INPUT_NONE || uiStyle->mUserInput == NS_STYLE_USER_INPUT_DISABLED)
   890     return nsFrame::HandleEvent(aPresContext, aEvent, aEventStatus);
   892   EventStates eventStates = mContent->AsElement()->State();
   893   if (eventStates.HasState(NS_EVENT_STATE_DISABLED))
   894     return NS_OK;
   896   return nsHTMLScrollFrame::HandleEvent(aPresContext, aEvent, aEventStatus);
   897 }
   900 //---------------------------------------------------------
   901 nsresult
   902 nsListControlFrame::SetInitialChildList(ChildListID    aListID,
   903                                         nsFrameList&   aChildList)
   904 {
   905   // First check to see if all the content has been added
   906   mIsAllContentHere = mContent->IsDoneAddingChildren();
   907   if (!mIsAllContentHere) {
   908     mIsAllFramesHere    = false;
   909     mHasBeenInitialized = false;
   910   }
   911   nsresult rv = nsHTMLScrollFrame::SetInitialChildList(aListID, aChildList);
   913   // If all the content is here now check
   914   // to see if all the frames have been created
   915   /*if (mIsAllContentHere) {
   916     // If all content and frames are here
   917     // the reset/initialize
   918     if (CheckIfAllFramesHere()) {
   919       ResetList(aPresContext);
   920       mHasBeenInitialized = true;
   921     }
   922   }*/
   924   return rv;
   925 }
   927 //---------------------------------------------------------
   928 void
   929 nsListControlFrame::Init(nsIContent*     aContent,
   930                          nsIFrame*       aParent,
   931                          nsIFrame*       aPrevInFlow)
   932 {
   933   nsHTMLScrollFrame::Init(aContent, aParent, aPrevInFlow);
   935   // we shouldn't have to unregister this listener because when
   936   // our frame goes away all these content node go away as well
   937   // because our frame is the only one who references them.
   938   // we need to hook up our listeners before the editor is initialized
   939   mEventListener = new nsListEventListener(this);
   941   mContent->AddSystemEventListener(NS_LITERAL_STRING("keydown"),
   942                                    mEventListener, false, false);
   943   mContent->AddSystemEventListener(NS_LITERAL_STRING("keypress"),
   944                                    mEventListener, false, false);
   945   mContent->AddSystemEventListener(NS_LITERAL_STRING("mousedown"),
   946                                    mEventListener, false, false);
   947   mContent->AddSystemEventListener(NS_LITERAL_STRING("mouseup"),
   948                                    mEventListener, false, false);
   949   mContent->AddSystemEventListener(NS_LITERAL_STRING("mousemove"),
   950                                    mEventListener, false, false);
   952   mStartSelectionIndex = kNothingSelected;
   953   mEndSelectionIndex = kNothingSelected;
   955   mLastDropdownBackstopColor = PresContext()->DefaultBackgroundColor();
   957   if (IsInDropDownMode()) {
   958     AddStateBits(NS_FRAME_IN_POPUP);
   959   }
   960 }
   962 dom::HTMLOptionsCollection*
   963 nsListControlFrame::GetOptions() const
   964 {
   965   dom::HTMLSelectElement* select =
   966     dom::HTMLSelectElement::FromContentOrNull(mContent);
   967   NS_ENSURE_TRUE(select, nullptr);
   969   return select->Options();
   970 }
   972 dom::HTMLOptionElement*
   973 nsListControlFrame::GetOption(uint32_t aIndex) const
   974 {
   975   dom::HTMLSelectElement* select =
   976     dom::HTMLSelectElement::FromContentOrNull(mContent);
   977   NS_ENSURE_TRUE(select, nullptr);
   979   return select->Item(aIndex);
   980 }
   982 NS_IMETHODIMP
   983 nsListControlFrame::OnOptionSelected(int32_t aIndex, bool aSelected)
   984 {
   985   if (aSelected) {
   986     ScrollToIndex(aIndex);
   987   }
   988   return NS_OK;
   989 }
   991 void
   992 nsListControlFrame::OnContentReset()
   993 {
   994   ResetList(true);
   995 }
   997 void 
   998 nsListControlFrame::ResetList(bool aAllowScrolling)
   999 {
  1000   // if all the frames aren't here 
  1001   // don't bother reseting
  1002   if (!mIsAllFramesHere) {
  1003     return;
  1006   if (aAllowScrolling) {
  1007     mPostChildrenLoadedReset = true;
  1009     // Scroll to the selected index
  1010     int32_t indexToSelect = kNothingSelected;
  1012     nsCOMPtr<nsIDOMHTMLSelectElement> selectElement(do_QueryInterface(mContent));
  1013     NS_ASSERTION(selectElement, "No select element!");
  1014     if (selectElement) {
  1015       selectElement->GetSelectedIndex(&indexToSelect);
  1016       nsWeakFrame weakFrame(this);
  1017       ScrollToIndex(indexToSelect);
  1018       if (!weakFrame.IsAlive()) {
  1019         return;
  1024   mStartSelectionIndex = kNothingSelected;
  1025   mEndSelectionIndex = kNothingSelected;
  1026   InvalidateFocus();
  1027   // Combobox will redisplay itself with the OnOptionSelected event
  1030 void 
  1031 nsListControlFrame::SetFocus(bool aOn, bool aRepaint)
  1033   InvalidateFocus();
  1035   if (aOn) {
  1036     ComboboxFocusSet();
  1037     mFocused = this;
  1038   } else {
  1039     mFocused = nullptr;
  1042   InvalidateFocus();
  1045 void nsListControlFrame::ComboboxFocusSet()
  1047   gLastKeyTime = 0;
  1050 void
  1051 nsListControlFrame::SetComboboxFrame(nsIFrame* aComboboxFrame)
  1053   if (nullptr != aComboboxFrame) {
  1054     mComboboxFrame = do_QueryFrame(aComboboxFrame);
  1058 void
  1059 nsListControlFrame::GetOptionText(uint32_t aIndex, nsAString& aStr)
  1061   aStr.Truncate();
  1062   if (dom::HTMLOptionElement* optionElement = GetOption(aIndex)) {
  1063     optionElement->GetText(aStr);
  1067 int32_t
  1068 nsListControlFrame::GetSelectedIndex()
  1070   dom::HTMLSelectElement* select =
  1071     dom::HTMLSelectElement::FromContentOrNull(mContent);
  1072   return select->SelectedIndex();
  1075 dom::HTMLOptionElement*
  1076 nsListControlFrame::GetCurrentOption()
  1078   // The mEndSelectionIndex is what is currently being selected. Use
  1079   // the selected index if this is kNothingSelected.
  1080   int32_t focusedIndex = (mEndSelectionIndex == kNothingSelected) ?
  1081     GetSelectedIndex() : mEndSelectionIndex;
  1083   if (focusedIndex != kNothingSelected) {
  1084     return GetOption(SafeCast<uint32_t>(focusedIndex));
  1087   // There is no selected item. Return the first non-disabled item.
  1088   nsRefPtr<dom::HTMLSelectElement> selectElement =
  1089     dom::HTMLSelectElement::FromContent(mContent);
  1091   for (uint32_t i = 0, length = selectElement->Length(); i < length; ++i) {
  1092     dom::HTMLOptionElement* node = selectElement->Item(i);
  1093     if (!node) {
  1094       return nullptr;
  1097     if (!selectElement->IsOptionDisabled(node)) {
  1098       return node;
  1102   return nullptr;
  1105 bool 
  1106 nsListControlFrame::IsInDropDownMode() const
  1108   return (mComboboxFrame != nullptr);
  1111 uint32_t
  1112 nsListControlFrame::GetNumberOfOptions()
  1114   dom::HTMLOptionsCollection* options = GetOptions();
  1115   if (!options) {
  1116     return 0;
  1119   return options->Length();
  1122 //----------------------------------------------------------------------
  1123 // nsISelectControlFrame
  1124 //----------------------------------------------------------------------
  1125 bool nsListControlFrame::CheckIfAllFramesHere()
  1127   // Get the number of optgroups and options
  1128   //int32_t numContentItems = 0;
  1129   nsCOMPtr<nsIDOMNode> node(do_QueryInterface(mContent));
  1130   if (node) {
  1131     // XXX Need to find a fail proff way to determine that
  1132     // all the frames are there
  1133     mIsAllFramesHere = true;//NS_OK == CountAllChild(node, numContentItems);
  1135   // now make sure we have a frame each piece of content
  1137   return mIsAllFramesHere;
  1140 NS_IMETHODIMP
  1141 nsListControlFrame::DoneAddingChildren(bool aIsDone)
  1143   mIsAllContentHere = aIsDone;
  1144   if (mIsAllContentHere) {
  1145     // Here we check to see if all the frames have been created 
  1146     // for all the content.
  1147     // If so, then we can initialize;
  1148     if (!mIsAllFramesHere) {
  1149       // if all the frames are now present we can initialize
  1150       if (CheckIfAllFramesHere()) {
  1151         mHasBeenInitialized = true;
  1152         ResetList(true);
  1156   return NS_OK;
  1159 NS_IMETHODIMP
  1160 nsListControlFrame::AddOption(int32_t aIndex)
  1162 #ifdef DO_REFLOW_DEBUG
  1163   printf("---- Id: %d nsLCF %p Added Option %d\n", mReflowId, this, aIndex);
  1164 #endif
  1166   if (!mIsAllContentHere) {
  1167     mIsAllContentHere = mContent->IsDoneAddingChildren();
  1168     if (!mIsAllContentHere) {
  1169       mIsAllFramesHere    = false;
  1170       mHasBeenInitialized = false;
  1171     } else {
  1172       mIsAllFramesHere = (aIndex == static_cast<int32_t>(GetNumberOfOptions()-1));
  1176   // Make sure we scroll to the selected option as needed
  1177   mNeedToReset = true;
  1179   if (!mHasBeenInitialized) {
  1180     return NS_OK;
  1183   mPostChildrenLoadedReset = mIsAllContentHere;
  1184   return NS_OK;
  1187 static int32_t
  1188 DecrementAndClamp(int32_t aSelectionIndex, int32_t aLength)
  1190   return aLength == 0 ? kNothingSelected : std::max(0, aSelectionIndex - 1);
  1193 NS_IMETHODIMP
  1194 nsListControlFrame::RemoveOption(int32_t aIndex)
  1196   NS_PRECONDITION(aIndex >= 0, "negative <option> index");
  1198   // Need to reset if we're a dropdown
  1199   if (IsInDropDownMode()) {
  1200     mNeedToReset = true;
  1201     mPostChildrenLoadedReset = mIsAllContentHere;
  1204   if (mStartSelectionIndex != kNothingSelected) {
  1205     NS_ASSERTION(mEndSelectionIndex != kNothingSelected, "");
  1206     int32_t numOptions = GetNumberOfOptions();
  1207     // NOTE: numOptions is the new number of options whereas aIndex is the
  1208     // unadjusted index of the removed option (hence the <= below).
  1209     NS_ASSERTION(aIndex <= numOptions, "out-of-bounds <option> index");
  1211     int32_t forward = mEndSelectionIndex - mStartSelectionIndex;
  1212     int32_t* low  = forward >= 0 ? &mStartSelectionIndex : &mEndSelectionIndex;
  1213     int32_t* high = forward >= 0 ? &mEndSelectionIndex : &mStartSelectionIndex;
  1214     if (aIndex < *low)
  1215       *low = ::DecrementAndClamp(*low, numOptions);
  1216     if (aIndex <= *high)
  1217       *high = ::DecrementAndClamp(*high, numOptions);
  1218     if (forward == 0)
  1219       *low = *high;
  1221   else
  1222     NS_ASSERTION(mEndSelectionIndex == kNothingSelected, "");
  1224   InvalidateFocus();
  1225   return NS_OK;
  1228 //---------------------------------------------------------
  1229 // Set the option selected in the DOM.  This method is named
  1230 // as it is because it indicates that the frame is the source
  1231 // of this event rather than the receiver.
  1232 bool
  1233 nsListControlFrame::SetOptionsSelectedFromFrame(int32_t aStartIndex,
  1234                                                 int32_t aEndIndex,
  1235                                                 bool aValue,
  1236                                                 bool aClearAll)
  1238   nsRefPtr<dom::HTMLSelectElement> selectElement =
  1239     dom::HTMLSelectElement::FromContent(mContent);
  1241   uint32_t mask = dom::HTMLSelectElement::NOTIFY;
  1242   if (aValue) {
  1243     mask |= dom::HTMLSelectElement::IS_SELECTED;
  1246   if (aClearAll) {
  1247     mask |= dom::HTMLSelectElement::CLEAR_ALL;
  1250   return selectElement->SetOptionsSelectedByIndex(aStartIndex, aEndIndex, mask);
  1253 bool
  1254 nsListControlFrame::ToggleOptionSelectedFromFrame(int32_t aIndex)
  1256   nsRefPtr<dom::HTMLOptionElement> option =
  1257     GetOption(static_cast<uint32_t>(aIndex));
  1258   NS_ENSURE_TRUE(option, false);
  1260   nsRefPtr<dom::HTMLSelectElement> selectElement =
  1261     dom::HTMLSelectElement::FromContent(mContent);
  1263   uint32_t mask = dom::HTMLSelectElement::NOTIFY;
  1264   if (!option->Selected()) {
  1265     mask |= dom::HTMLSelectElement::IS_SELECTED;
  1268   return selectElement->SetOptionsSelectedByIndex(aIndex, aIndex, mask);
  1272 // Dispatch event and such
  1273 bool
  1274 nsListControlFrame::UpdateSelection()
  1276   if (mIsAllFramesHere) {
  1277     // if it's a combobox, display the new text
  1278     nsWeakFrame weakFrame(this);
  1279     if (mComboboxFrame) {
  1280       mComboboxFrame->RedisplaySelectedText();
  1282     // if it's a listbox, fire on change
  1283     else if (mIsAllContentHere) {
  1284       FireOnChange();
  1286     return weakFrame.IsAlive();
  1288   return true;
  1291 void
  1292 nsListControlFrame::ComboboxFinish(int32_t aIndex)
  1294   gLastKeyTime = 0;
  1296   if (mComboboxFrame) {
  1297     nsWeakFrame weakFrame(this);
  1298     PerformSelection(aIndex, false, false);  // might destroy us
  1299     if (!weakFrame.IsAlive() || !mComboboxFrame) {
  1300       return;
  1303     int32_t displayIndex = mComboboxFrame->GetIndexOfDisplayArea();
  1304     if (displayIndex != aIndex) {
  1305       mComboboxFrame->RedisplaySelectedText(); // might destroy us
  1308     if (weakFrame.IsAlive() && mComboboxFrame) {
  1309       mComboboxFrame->RollupFromList(); // might destroy us
  1314 // Send out an onchange notification.
  1315 void
  1316 nsListControlFrame::FireOnChange()
  1318   if (mComboboxFrame) {
  1319     // Return hit without changing anything
  1320     int32_t index = mComboboxFrame->UpdateRecentIndex(NS_SKIP_NOTIFY_INDEX);
  1321     if (index == NS_SKIP_NOTIFY_INDEX)
  1322       return;
  1324     // See if the selection actually changed
  1325     if (index == GetSelectedIndex())
  1326       return;
  1329   // Dispatch the change event.
  1330   nsContentUtils::DispatchTrustedEvent(mContent->OwnerDoc(), mContent,
  1331                                        NS_LITERAL_STRING("change"), true,
  1332                                        false);
  1335 NS_IMETHODIMP
  1336 nsListControlFrame::OnSetSelectedIndex(int32_t aOldIndex, int32_t aNewIndex)
  1338   if (mComboboxFrame) {
  1339     // UpdateRecentIndex with NS_SKIP_NOTIFY_INDEX, so that we won't fire an onchange
  1340     // event for this setting of selectedIndex.
  1341     mComboboxFrame->UpdateRecentIndex(NS_SKIP_NOTIFY_INDEX);
  1344   nsWeakFrame weakFrame(this);
  1345   ScrollToIndex(aNewIndex);
  1346   if (!weakFrame.IsAlive()) {
  1347     return NS_OK;
  1349   mStartSelectionIndex = aNewIndex;
  1350   mEndSelectionIndex = aNewIndex;
  1351   InvalidateFocus();
  1353 #ifdef ACCESSIBILITY
  1354   FireMenuItemActiveEvent();
  1355 #endif
  1357   return NS_OK;
  1360 //----------------------------------------------------------------------
  1361 // End nsISelectControlFrame
  1362 //----------------------------------------------------------------------
  1364 nsresult
  1365 nsListControlFrame::SetFormProperty(nsIAtom* aName,
  1366                                 const nsAString& aValue)
  1368   if (nsGkAtoms::selected == aName) {
  1369     return NS_ERROR_INVALID_ARG; // Selected is readonly according to spec.
  1370   } else if (nsGkAtoms::selectedindex == aName) {
  1371     // You shouldn't be calling me for this!!!
  1372     return NS_ERROR_INVALID_ARG;
  1375   // We should be told about selectedIndex by the DOM element through
  1376   // OnOptionSelected
  1378   return NS_OK;
  1381 void
  1382 nsListControlFrame::AboutToDropDown()
  1384   NS_ASSERTION(IsInDropDownMode(),
  1385     "AboutToDropDown called without being in dropdown mode");
  1387   // Our widget doesn't get invalidated on changes to the rest of the document,
  1388   // so compute and store this color at the start of a dropdown so we don't
  1389   // get weird painting behaviour.
  1390   // We start looking for backgrounds above the combobox frame to avoid
  1391   // duplicating the combobox frame's background and compose each background
  1392   // color we find underneath until we have an opaque color, or run out of
  1393   // backgrounds. We compose with the PresContext default background color,
  1394   // which is always opaque, in case we don't end up with an opaque color.
  1395   // This gives us a very poor approximation of translucency.
  1396   nsIFrame* comboboxFrame = do_QueryFrame(mComboboxFrame);
  1397   nsStyleContext* context = comboboxFrame->StyleContext()->GetParent();
  1398   mLastDropdownBackstopColor = NS_RGBA(0,0,0,0);
  1399   while (NS_GET_A(mLastDropdownBackstopColor) < 255 && context) {
  1400     mLastDropdownBackstopColor =
  1401       NS_ComposeColors(context->StyleBackground()->mBackgroundColor,
  1402                        mLastDropdownBackstopColor);
  1403     context = context->GetParent();
  1405   mLastDropdownBackstopColor =
  1406     NS_ComposeColors(PresContext()->DefaultBackgroundColor(),
  1407                      mLastDropdownBackstopColor);
  1409   if (mIsAllContentHere && mIsAllFramesHere && mHasBeenInitialized) {
  1410     nsWeakFrame weakFrame(this);
  1411     ScrollToIndex(GetSelectedIndex());
  1412     if (!weakFrame.IsAlive()) {
  1413       return;
  1415 #ifdef ACCESSIBILITY
  1416     FireMenuItemActiveEvent(); // Inform assistive tech what got focus
  1417 #endif
  1419   mItemSelectionStarted = false;
  1422 // We are about to be rolledup from the outside (ComboboxFrame)
  1423 void
  1424 nsListControlFrame::AboutToRollup()
  1426   // We've been updating the combobox with the keyboard up until now, but not
  1427   // with the mouse.  The problem is, even with mouse selection, we are
  1428   // updating the <select>.  So if the mouse goes over an option just before
  1429   // he leaves the box and clicks, that's what the <select> will show.
  1430   //
  1431   // To deal with this we say "whatever is in the combobox is canonical."
  1432   // - IF the combobox is different from the current selected index, we
  1433   //   reset the index.
  1435   if (IsInDropDownMode()) {
  1436     ComboboxFinish(mComboboxFrame->GetIndexOfDisplayArea()); // might destroy us
  1440 nsresult
  1441 nsListControlFrame::DidReflow(nsPresContext*           aPresContext,
  1442                               const nsHTMLReflowState* aReflowState,
  1443                               nsDidReflowStatus        aStatus)
  1445   nsresult rv;
  1446   bool wasInterrupted = !mHasPendingInterruptAtStartOfReflow &&
  1447                           aPresContext->HasPendingInterrupt();
  1449   rv = nsHTMLScrollFrame::DidReflow(aPresContext, aReflowState, aStatus);
  1451   if (mNeedToReset && !wasInterrupted) {
  1452     mNeedToReset = false;
  1453     // Suppress scrolling to the selected element if we restored
  1454     // scroll history state AND the list contents have not changed
  1455     // since we loaded all the children AND nothing else forced us
  1456     // to scroll by calling ResetList(true). The latter two conditions
  1457     // are folded into mPostChildrenLoadedReset.
  1458     //
  1459     // The idea is that we want scroll history restoration to trump ResetList
  1460     // scrolling to the selected element, when the ResetList was probably only
  1461     // caused by content loading normally.
  1462     ResetList(!DidHistoryRestore() || mPostChildrenLoadedReset);
  1465   mHasPendingInterruptAtStartOfReflow = false;
  1466   return rv;
  1469 nsIAtom*
  1470 nsListControlFrame::GetType() const
  1472   return nsGkAtoms::listControlFrame; 
  1475 #ifdef DEBUG_FRAME_DUMP
  1476 nsresult
  1477 nsListControlFrame::GetFrameName(nsAString& aResult) const
  1479   return MakeFrameName(NS_LITERAL_STRING("ListControl"), aResult);
  1481 #endif
  1483 nscoord
  1484 nsListControlFrame::GetHeightOfARow()
  1486   return HeightOfARow();
  1489 nsresult
  1490 nsListControlFrame::IsOptionDisabled(int32_t anIndex, bool &aIsDisabled)
  1492   nsRefPtr<dom::HTMLSelectElement> sel =
  1493     dom::HTMLSelectElement::FromContent(mContent);
  1494   if (sel) {
  1495     sel->IsOptionDisabled(anIndex, &aIsDisabled);
  1496     return NS_OK;
  1498   return NS_ERROR_FAILURE;
  1501 //----------------------------------------------------------------------
  1502 // helper
  1503 //----------------------------------------------------------------------
  1504 bool
  1505 nsListControlFrame::IsLeftButton(nsIDOMEvent* aMouseEvent)
  1507   // only allow selection with the left button
  1508   nsCOMPtr<nsIDOMMouseEvent> mouseEvent = do_QueryInterface(aMouseEvent);
  1509   if (mouseEvent) {
  1510     int16_t whichButton;
  1511     if (NS_SUCCEEDED(mouseEvent->GetButton(&whichButton))) {
  1512       return whichButton != 0?false:true;
  1515   return false;
  1518 nscoord
  1519 nsListControlFrame::CalcFallbackRowHeight(float aFontSizeInflation)
  1521   nscoord rowHeight = 0;
  1523   nsRefPtr<nsFontMetrics> fontMet;
  1524   nsLayoutUtils::GetFontMetricsForFrame(this, getter_AddRefs(fontMet),
  1525                                         aFontSizeInflation);
  1526   if (fontMet) {
  1527     rowHeight = fontMet->MaxHeight();
  1530   return rowHeight;
  1533 nscoord
  1534 nsListControlFrame::CalcIntrinsicHeight(nscoord aHeightOfARow,
  1535                                         int32_t aNumberOfOptions)
  1537   NS_PRECONDITION(!IsInDropDownMode(),
  1538                   "Shouldn't be in dropdown mode when we call this");
  1540   dom::HTMLSelectElement* select =
  1541     dom::HTMLSelectElement::FromContentOrNull(mContent);
  1542   if (select) {
  1543     mNumDisplayRows = select->Size();
  1544   } else {
  1545     mNumDisplayRows = 1;
  1548   if (mNumDisplayRows < 1) {
  1549     mNumDisplayRows = 4;
  1552   return mNumDisplayRows * aHeightOfARow;
  1555 //----------------------------------------------------------------------
  1556 // nsIDOMMouseListener
  1557 //----------------------------------------------------------------------
  1558 nsresult
  1559 nsListControlFrame::MouseUp(nsIDOMEvent* aMouseEvent)
  1561   NS_ASSERTION(aMouseEvent != nullptr, "aMouseEvent is null.");
  1563   nsCOMPtr<nsIDOMMouseEvent> mouseEvent = do_QueryInterface(aMouseEvent);
  1564   NS_ENSURE_TRUE(mouseEvent, NS_ERROR_FAILURE);
  1566   UpdateInListState(aMouseEvent);
  1568   mButtonDown = false;
  1570   EventStates eventStates = mContent->AsElement()->State();
  1571   if (eventStates.HasState(NS_EVENT_STATE_DISABLED)) {
  1572     return NS_OK;
  1575   // only allow selection with the left button
  1576   // if a right button click is on the combobox itself
  1577   // or on the select when in listbox mode, then let the click through
  1578   if (!IsLeftButton(aMouseEvent)) {
  1579     if (IsInDropDownMode()) {
  1580       if (!IgnoreMouseEventForSelection(aMouseEvent)) {
  1581         aMouseEvent->PreventDefault();
  1582         aMouseEvent->StopPropagation();
  1583       } else {
  1584         CaptureMouseEvents(false);
  1585         return NS_OK;
  1587       CaptureMouseEvents(false);
  1588       return NS_ERROR_FAILURE; // means consume event
  1589     } else {
  1590       CaptureMouseEvents(false);
  1591       return NS_OK;
  1595   const nsStyleVisibility* vis = StyleVisibility();
  1597   if (!vis->IsVisible()) {
  1598     return NS_OK;
  1601   if (IsInDropDownMode()) {
  1602     // XXX This is a bit of a hack, but.....
  1603     // But the idea here is to make sure you get an "onclick" event when you mouse
  1604     // down on the select and the drag over an option and let go
  1605     // And then NOT get an "onclick" event when when you click down on the select
  1606     // and then up outside of the select
  1607     // the EventStateManager tracks the content of the mouse down and the mouse up
  1608     // to make sure they are the same, and the onclick is sent in the PostHandleEvent
  1609     // depeneding on whether the clickCount is non-zero.
  1610     // So we cheat here by either setting or unsetting the clcikCount in the native event
  1611     // so the right thing happens for the onclick event
  1612     WidgetMouseEvent* mouseEvent =
  1613       aMouseEvent->GetInternalNSEvent()->AsMouseEvent();
  1615     int32_t selectedIndex;
  1616     if (NS_SUCCEEDED(GetIndexFromDOMEvent(aMouseEvent, selectedIndex))) {
  1617       // If it's disabled, disallow the click and leave.
  1618       bool isDisabled = false;
  1619       IsOptionDisabled(selectedIndex, isDisabled);
  1620       if (isDisabled) {
  1621         aMouseEvent->PreventDefault();
  1622         aMouseEvent->StopPropagation();
  1623         CaptureMouseEvents(false);
  1624         return NS_ERROR_FAILURE;
  1627       if (kNothingSelected != selectedIndex) {
  1628         nsWeakFrame weakFrame(this);
  1629         ComboboxFinish(selectedIndex);
  1630         if (!weakFrame.IsAlive())
  1631           return NS_OK;
  1632         FireOnChange();
  1635       mouseEvent->clickCount = 1;
  1636     } else {
  1637       // the click was out side of the select or its dropdown
  1638       mouseEvent->clickCount = IgnoreMouseEventForSelection(aMouseEvent) ? 1 : 0;
  1640   } else {
  1641     CaptureMouseEvents(false);
  1642     // Notify
  1643     if (mChangesSinceDragStart) {
  1644       // reset this so that future MouseUps without a prior MouseDown
  1645       // won't fire onchange
  1646       mChangesSinceDragStart = false;
  1647       FireOnChange();
  1651   return NS_OK;
  1654 void
  1655 nsListControlFrame::UpdateInListState(nsIDOMEvent* aEvent)
  1657   if (!mComboboxFrame || !mComboboxFrame->IsDroppedDown())
  1658     return;
  1660   nsPoint pt = nsLayoutUtils::GetDOMEventCoordinatesRelativeTo(aEvent, this);
  1661   nsRect borderInnerEdge = GetScrollPortRect();
  1662   if (pt.y >= borderInnerEdge.y && pt.y < borderInnerEdge.YMost()) {
  1663     mItemSelectionStarted = true;
  1667 bool nsListControlFrame::IgnoreMouseEventForSelection(nsIDOMEvent* aEvent)
  1669   if (!mComboboxFrame)
  1670     return false;
  1672   // Our DOM listener does get called when the dropdown is not
  1673   // showing, because it listens to events on the SELECT element
  1674   if (!mComboboxFrame->IsDroppedDown())
  1675     return true;
  1677   return !mItemSelectionStarted;
  1680 #ifdef ACCESSIBILITY
  1681 void
  1682 nsListControlFrame::FireMenuItemActiveEvent()
  1684   if (mFocused != this && !IsInDropDownMode()) {
  1685     return;
  1688   nsCOMPtr<nsIContent> optionContent = GetCurrentOption();
  1689   if (!optionContent) {
  1690     return;
  1693   FireDOMEvent(NS_LITERAL_STRING("DOMMenuItemActive"), optionContent);
  1695 #endif
  1697 nsresult
  1698 nsListControlFrame::GetIndexFromDOMEvent(nsIDOMEvent* aMouseEvent, 
  1699                                          int32_t&     aCurIndex)
  1701   if (IgnoreMouseEventForSelection(aMouseEvent))
  1702     return NS_ERROR_FAILURE;
  1704   if (nsIPresShell::GetCapturingContent() != mContent) {
  1705     // If we're not capturing, then ignore movement in the border
  1706     nsPoint pt = nsLayoutUtils::GetDOMEventCoordinatesRelativeTo(aMouseEvent, this);
  1707     nsRect borderInnerEdge = GetScrollPortRect();
  1708     if (!borderInnerEdge.Contains(pt)) {
  1709       return NS_ERROR_FAILURE;
  1713   nsRefPtr<dom::HTMLOptionElement> option;
  1714   for (nsCOMPtr<nsIContent> content =
  1715          PresContext()->EventStateManager()->GetEventTargetContent(nullptr);
  1716        content && !option;
  1717        content = content->GetParent()) {
  1718     option = dom::HTMLOptionElement::FromContent(content);
  1721   if (option) {
  1722     aCurIndex = option->Index();
  1723     MOZ_ASSERT(aCurIndex >= 0);
  1724     return NS_OK;
  1727   int32_t numOptions = GetNumberOfOptions();
  1728   if (numOptions < 1)
  1729     return NS_ERROR_FAILURE;
  1731   nsPoint pt = nsLayoutUtils::GetDOMEventCoordinatesRelativeTo(aMouseEvent, this);
  1733   // If the event coordinate is above the first option frame, then target the
  1734   // first option frame
  1735   nsRefPtr<dom::HTMLOptionElement> firstOption = GetOption(0);
  1736   NS_ASSERTION(firstOption, "Can't find first option that's supposed to be there");
  1737   nsIFrame* optionFrame = firstOption->GetPrimaryFrame();
  1738   if (optionFrame) {
  1739     nsPoint ptInOptionFrame = pt - optionFrame->GetOffsetTo(this);
  1740     if (ptInOptionFrame.y < 0 && ptInOptionFrame.x >= 0 &&
  1741         ptInOptionFrame.x < optionFrame->GetSize().width) {
  1742       aCurIndex = 0;
  1743       return NS_OK;
  1747   nsRefPtr<dom::HTMLOptionElement> lastOption = GetOption(numOptions - 1);
  1748   // If the event coordinate is below the last option frame, then target the
  1749   // last option frame
  1750   NS_ASSERTION(lastOption, "Can't find last option that's supposed to be there");
  1751   optionFrame = lastOption->GetPrimaryFrame();
  1752   if (optionFrame) {
  1753     nsPoint ptInOptionFrame = pt - optionFrame->GetOffsetTo(this);
  1754     if (ptInOptionFrame.y >= optionFrame->GetSize().height && ptInOptionFrame.x >= 0 &&
  1755         ptInOptionFrame.x < optionFrame->GetSize().width) {
  1756       aCurIndex = numOptions - 1;
  1757       return NS_OK;
  1761   return NS_ERROR_FAILURE;
  1764 nsresult
  1765 nsListControlFrame::MouseDown(nsIDOMEvent* aMouseEvent)
  1767   NS_ASSERTION(aMouseEvent != nullptr, "aMouseEvent is null.");
  1769   nsCOMPtr<nsIDOMMouseEvent> mouseEvent = do_QueryInterface(aMouseEvent);
  1770   NS_ENSURE_TRUE(mouseEvent, NS_ERROR_FAILURE);
  1772   UpdateInListState(aMouseEvent);
  1774   EventStates eventStates = mContent->AsElement()->State();
  1775   if (eventStates.HasState(NS_EVENT_STATE_DISABLED)) {
  1776     return NS_OK;
  1779   // only allow selection with the left button
  1780   // if a right button click is on the combobox itself
  1781   // or on the select when in listbox mode, then let the click through
  1782   if (!IsLeftButton(aMouseEvent)) {
  1783     if (IsInDropDownMode()) {
  1784       if (!IgnoreMouseEventForSelection(aMouseEvent)) {
  1785         aMouseEvent->PreventDefault();
  1786         aMouseEvent->StopPropagation();
  1787       } else {
  1788         return NS_OK;
  1790       return NS_ERROR_FAILURE; // means consume event
  1791     } else {
  1792       return NS_OK;
  1796   int32_t selectedIndex;
  1797   if (NS_SUCCEEDED(GetIndexFromDOMEvent(aMouseEvent, selectedIndex))) {
  1798     // Handle Like List
  1799     mButtonDown = true;
  1800     CaptureMouseEvents(true);
  1801     nsWeakFrame weakFrame(this);
  1802     bool change =
  1803       HandleListSelection(aMouseEvent, selectedIndex); // might destroy us
  1804     if (!weakFrame.IsAlive()) {
  1805       return NS_OK;
  1807     mChangesSinceDragStart = change;
  1808   } else {
  1809     // NOTE: the combo box is responsible for dropping it down
  1810     if (mComboboxFrame) {
  1811       if (XRE_GetProcessType() == GeckoProcessType_Content && BrowserTabsRemote()) {
  1812         nsContentUtils::DispatchChromeEvent(mContent->OwnerDoc(), mContent,
  1813                                             NS_LITERAL_STRING("mozshowdropdown"), true,
  1814                                             false);
  1815         return NS_OK;
  1818       if (!IgnoreMouseEventForSelection(aMouseEvent)) {
  1819         return NS_OK;
  1822       if (!nsComboboxControlFrame::ToolkitHasNativePopup())
  1824         bool isDroppedDown = mComboboxFrame->IsDroppedDown();
  1825         nsIFrame* comboFrame = do_QueryFrame(mComboboxFrame);
  1826         nsWeakFrame weakFrame(comboFrame);
  1827         mComboboxFrame->ShowDropDown(!isDroppedDown);
  1828         if (!weakFrame.IsAlive())
  1829           return NS_OK;
  1830         if (isDroppedDown) {
  1831           CaptureMouseEvents(false);
  1837   return NS_OK;
  1840 //----------------------------------------------------------------------
  1841 // nsIDOMMouseMotionListener
  1842 //----------------------------------------------------------------------
  1843 nsresult
  1844 nsListControlFrame::MouseMove(nsIDOMEvent* aMouseEvent)
  1846   NS_ASSERTION(aMouseEvent, "aMouseEvent is null.");
  1847   nsCOMPtr<nsIDOMMouseEvent> mouseEvent = do_QueryInterface(aMouseEvent);
  1848   NS_ENSURE_TRUE(mouseEvent, NS_ERROR_FAILURE);
  1850   UpdateInListState(aMouseEvent);
  1852   if (IsInDropDownMode()) { 
  1853     if (mComboboxFrame->IsDroppedDown()) {
  1854       int32_t selectedIndex;
  1855       if (NS_SUCCEEDED(GetIndexFromDOMEvent(aMouseEvent, selectedIndex))) {
  1856         PerformSelection(selectedIndex, false, false); // might destroy us
  1859   } else {// XXX - temporary until we get drag events
  1860     if (mButtonDown) {
  1861       return DragMove(aMouseEvent); // might destroy us
  1864   return NS_OK;
  1867 nsresult
  1868 nsListControlFrame::DragMove(nsIDOMEvent* aMouseEvent)
  1870   NS_ASSERTION(aMouseEvent, "aMouseEvent is null.");
  1872   UpdateInListState(aMouseEvent);
  1874   if (!IsInDropDownMode()) { 
  1875     int32_t selectedIndex;
  1876     if (NS_SUCCEEDED(GetIndexFromDOMEvent(aMouseEvent, selectedIndex))) {
  1877       // Don't waste cycles if we already dragged over this item
  1878       if (selectedIndex == mEndSelectionIndex) {
  1879         return NS_OK;
  1881       nsCOMPtr<nsIDOMMouseEvent> mouseEvent = do_QueryInterface(aMouseEvent);
  1882       NS_ASSERTION(mouseEvent, "aMouseEvent is not an nsIDOMMouseEvent!");
  1883       bool isControl;
  1884 #ifdef XP_MACOSX
  1885       mouseEvent->GetMetaKey(&isControl);
  1886 #else
  1887       mouseEvent->GetCtrlKey(&isControl);
  1888 #endif
  1889       nsWeakFrame weakFrame(this);
  1890       // Turn SHIFT on when you are dragging, unless control is on.
  1891       bool wasChanged = PerformSelection(selectedIndex,
  1892                                            !isControl, isControl);
  1893       if (!weakFrame.IsAlive()) {
  1894         return NS_OK;
  1896       mChangesSinceDragStart = mChangesSinceDragStart || wasChanged;
  1899   return NS_OK;
  1902 //----------------------------------------------------------------------
  1903 // Scroll helpers.
  1904 //----------------------------------------------------------------------
  1905 void
  1906 nsListControlFrame::ScrollToIndex(int32_t aIndex)
  1908   if (aIndex < 0) {
  1909     // XXX shouldn't we just do nothing if we're asked to scroll to
  1910     // kNothingSelected?
  1911     ScrollTo(nsPoint(0, 0), nsIScrollableFrame::INSTANT);
  1912   } else {
  1913     nsRefPtr<dom::HTMLOptionElement> option =
  1914       GetOption(SafeCast<uint32_t>(aIndex));
  1915     if (option) {
  1916       ScrollToFrame(*option);
  1921 void
  1922 nsListControlFrame::ScrollToFrame(dom::HTMLOptionElement& aOptElement)
  1924   // otherwise we find the content's frame and scroll to it
  1925   nsIFrame* childFrame = aOptElement.GetPrimaryFrame();
  1926   if (childFrame) {
  1927     PresContext()->PresShell()->
  1928       ScrollFrameRectIntoView(childFrame,
  1929                               nsRect(nsPoint(0, 0), childFrame->GetSize()),
  1930                               nsIPresShell::ScrollAxis(), nsIPresShell::ScrollAxis(),
  1931                               nsIPresShell::SCROLL_OVERFLOW_HIDDEN |
  1932                               nsIPresShell::SCROLL_FIRST_ANCESTOR_ONLY);
  1936 //---------------------------------------------------------------------
  1937 // Ok, the entire idea of this routine is to move to the next item that 
  1938 // is suppose to be selected. If the item is disabled then we search in 
  1939 // the same direction looking for the next item to select. If we run off 
  1940 // the end of the list then we start at the end of the list and search 
  1941 // backwards until we get back to the original item or an enabled option
  1942 // 
  1943 // aStartIndex - the index to start searching from
  1944 // aNewIndex - will get set to the new index if it finds one
  1945 // aNumOptions - the total number of options in the list
  1946 // aDoAdjustInc - the initial increment 1-n
  1947 // aDoAdjustIncNext - the increment used to search for the next enabled option
  1948 //
  1949 // the aDoAdjustInc could be a "1" for a single item or
  1950 // any number greater representing a page of items
  1951 //
  1952 void
  1953 nsListControlFrame::AdjustIndexForDisabledOpt(int32_t aStartIndex,
  1954                                               int32_t &aNewIndex,
  1955                                               int32_t aNumOptions,
  1956                                               int32_t aDoAdjustInc,
  1957                                               int32_t aDoAdjustIncNext)
  1959   // Cannot select anything if there is nothing to select
  1960   if (aNumOptions == 0) {
  1961     aNewIndex = kNothingSelected;
  1962     return;
  1965   // means we reached the end of the list and now we are searching backwards
  1966   bool doingReverse = false;
  1967   // lowest index in the search range
  1968   int32_t bottom      = 0;
  1969   // highest index in the search range
  1970   int32_t top         = aNumOptions;
  1972   // Start off keyboard options at selectedIndex if nothing else is defaulted to
  1973   //
  1974   // XXX Perhaps this should happen for mouse too, to start off shift click
  1975   // automatically in multiple ... to do this, we'd need to override
  1976   // OnOptionSelected and set mStartSelectedIndex if nothing is selected.  Not
  1977   // sure of the effects, though, so I'm not doing it just yet.
  1978   int32_t startIndex = aStartIndex;
  1979   if (startIndex < bottom) {
  1980     startIndex = GetSelectedIndex();
  1982   int32_t newIndex    = startIndex + aDoAdjustInc;
  1984   // make sure we start off in the range
  1985   if (newIndex < bottom) {
  1986     newIndex = 0;
  1987   } else if (newIndex >= top) {
  1988     newIndex = aNumOptions-1;
  1991   while (1) {
  1992     // if the newIndex isn't disabled, we are golden, bail out
  1993     bool isDisabled = true;
  1994     if (NS_SUCCEEDED(IsOptionDisabled(newIndex, isDisabled)) && !isDisabled) {
  1995       break;
  1998     // it WAS disabled, so sart looking ahead for the next enabled option
  1999     newIndex += aDoAdjustIncNext;
  2001     // well, if we reach end reverse the search
  2002     if (newIndex < bottom) {
  2003       if (doingReverse) {
  2004         return; // if we are in reverse mode and reach the end bail out
  2005       } else {
  2006         // reset the newIndex to the end of the list we hit
  2007         // reverse the incrementer
  2008         // set the other end of the list to our original starting index
  2009         newIndex         = bottom;
  2010         aDoAdjustIncNext = 1;
  2011         doingReverse     = true;
  2012         top              = startIndex;
  2014     } else  if (newIndex >= top) {
  2015       if (doingReverse) {
  2016         return;        // if we are in reverse mode and reach the end bail out
  2017       } else {
  2018         // reset the newIndex to the end of the list we hit
  2019         // reverse the incrementer
  2020         // set the other end of the list to our original starting index
  2021         newIndex = top - 1;
  2022         aDoAdjustIncNext = -1;
  2023         doingReverse     = true;
  2024         bottom           = startIndex;
  2029   // Looks like we found one
  2030   aNewIndex     = newIndex;
  2033 nsAString& 
  2034 nsListControlFrame::GetIncrementalString()
  2036   if (sIncrementalString == nullptr)
  2037     sIncrementalString = new nsString();
  2039   return *sIncrementalString;
  2042 void
  2043 nsListControlFrame::Shutdown()
  2045   delete sIncrementalString;
  2046   sIncrementalString = nullptr;
  2049 void
  2050 nsListControlFrame::DropDownToggleKey(nsIDOMEvent* aKeyEvent)
  2052   // Cocoa widgets do native popups, so don't try to show
  2053   // dropdowns there.
  2054   if (IsInDropDownMode() && !nsComboboxControlFrame::ToolkitHasNativePopup()) {
  2055     aKeyEvent->PreventDefault();
  2056     if (!mComboboxFrame->IsDroppedDown()) {
  2057       mComboboxFrame->ShowDropDown(true);
  2058     } else {
  2059       nsWeakFrame weakFrame(this);
  2060       // mEndSelectionIndex is the last item that got selected.
  2061       ComboboxFinish(mEndSelectionIndex);
  2062       if (weakFrame.IsAlive()) {
  2063         FireOnChange();
  2069 nsresult
  2070 nsListControlFrame::KeyDown(nsIDOMEvent* aKeyEvent)
  2072   MOZ_ASSERT(aKeyEvent, "aKeyEvent is null.");
  2074   EventStates eventStates = mContent->AsElement()->State();
  2075   if (eventStates.HasState(NS_EVENT_STATE_DISABLED)) {
  2076     return NS_OK;
  2079   AutoIncrementalSearchResetter incrementalSearchResetter;
  2081   // Don't check defaultPrevented value because other browsers don't prevent
  2082   // the key navigation of list control even if preventDefault() is called.
  2084   const WidgetKeyboardEvent* keyEvent =
  2085     aKeyEvent->GetInternalNSEvent()->AsKeyboardEvent();
  2086   MOZ_ASSERT(keyEvent,
  2087     "DOM event must have WidgetKeyboardEvent for its internal event");
  2089   if (keyEvent->IsAlt()) {
  2090     if (keyEvent->keyCode == NS_VK_UP || keyEvent->keyCode == NS_VK_DOWN) {
  2091       DropDownToggleKey(aKeyEvent);
  2093     return NS_OK;
  2096   // now make sure there are options or we are wasting our time
  2097   nsRefPtr<dom::HTMLOptionsCollection> options = GetOptions();
  2098   NS_ENSURE_TRUE(options, NS_ERROR_FAILURE);
  2100   uint32_t numOptions = options->Length();
  2102   // this is the new index to set
  2103   int32_t newIndex = kNothingSelected;
  2105   bool isControlOrMeta = (keyEvent->IsControl() || keyEvent->IsMeta());
  2106   // Don't try to handle multiple-select pgUp/pgDown in single-select lists.
  2107   if (isControlOrMeta && !GetMultiple() &&
  2108       (keyEvent->keyCode == NS_VK_PAGE_UP ||
  2109        keyEvent->keyCode == NS_VK_PAGE_DOWN)) {
  2110     return NS_OK;
  2112   if (isControlOrMeta && (keyEvent->keyCode == NS_VK_UP ||
  2113                           keyEvent->keyCode == NS_VK_LEFT ||
  2114                           keyEvent->keyCode == NS_VK_DOWN ||
  2115                           keyEvent->keyCode == NS_VK_RIGHT ||
  2116                           keyEvent->keyCode == NS_VK_HOME ||
  2117                           keyEvent->keyCode == NS_VK_END)) {
  2118     // Don't go into multiple-select mode unless this list can handle it.
  2119     isControlOrMeta = mControlSelectMode = GetMultiple();
  2120   } else if (keyEvent->keyCode != NS_VK_SPACE) {
  2121     mControlSelectMode = false;
  2124   switch (keyEvent->keyCode) {
  2125     case NS_VK_UP:
  2126     case NS_VK_LEFT:
  2127       AdjustIndexForDisabledOpt(mEndSelectionIndex, newIndex,
  2128                                 static_cast<int32_t>(numOptions),
  2129                                 -1, -1);
  2130       break;
  2131     case NS_VK_DOWN:
  2132     case NS_VK_RIGHT:
  2133       AdjustIndexForDisabledOpt(mEndSelectionIndex, newIndex,
  2134                                 static_cast<int32_t>(numOptions),
  2135                                 1, 1);
  2136       break;
  2137     case NS_VK_RETURN:
  2138       if (IsInDropDownMode()) {
  2139         if (mComboboxFrame->IsDroppedDown()) {
  2140           // If the select element is a dropdown style, Enter key should be
  2141           // consumed while the dropdown is open for security.
  2142           aKeyEvent->PreventDefault();
  2144           nsWeakFrame weakFrame(this);
  2145           ComboboxFinish(mEndSelectionIndex);
  2146           if (!weakFrame.IsAlive()) {
  2147             return NS_OK;
  2150         // XXX This is strange. On other browsers, "change" event is fired
  2151         //     immediately after the selected item is changed rather than
  2152         //     Enter key is pressed.
  2153         FireOnChange();
  2154         return NS_OK;
  2157       // If this is single select listbox, Enter key doesn't cause anything.
  2158       if (!GetMultiple()) {
  2159         return NS_OK;
  2162       newIndex = mEndSelectionIndex;
  2163       break;
  2164     case NS_VK_ESCAPE: {
  2165       // If the select element is a listbox style, Escape key causes nothing.
  2166       if (!IsInDropDownMode()) {
  2167         return NS_OK;
  2170       AboutToRollup();
  2171       // If the select element is a dropdown style, Enter key should be
  2172       // consumed everytime since Escape key may be pressed accidentally after
  2173       // the dropdown is closed by Escepe key.
  2174       aKeyEvent->PreventDefault();
  2175       return NS_OK;
  2177     case NS_VK_PAGE_UP: {
  2178       int32_t itemsPerPage =
  2179         std::max(1, static_cast<int32_t>(mNumDisplayRows - 1));
  2180       AdjustIndexForDisabledOpt(mEndSelectionIndex, newIndex,
  2181                                 static_cast<int32_t>(numOptions),
  2182                                 -itemsPerPage, -1);
  2183       break;
  2185     case NS_VK_PAGE_DOWN: {
  2186       int32_t itemsPerPage =
  2187         std::max(1, static_cast<int32_t>(mNumDisplayRows - 1));
  2188       AdjustIndexForDisabledOpt(mEndSelectionIndex, newIndex,
  2189                                 static_cast<int32_t>(numOptions),
  2190                                 itemsPerPage, 1);
  2191       break;
  2193     case NS_VK_HOME:
  2194       AdjustIndexForDisabledOpt(0, newIndex,
  2195                                 static_cast<int32_t>(numOptions),
  2196                                 0, 1);
  2197       break;
  2198     case NS_VK_END:
  2199       AdjustIndexForDisabledOpt(static_cast<int32_t>(numOptions) - 1, newIndex,
  2200                                 static_cast<int32_t>(numOptions),
  2201                                 0, -1);
  2202       break;
  2204 #if defined(XP_WIN)
  2205     case NS_VK_F4:
  2206       if (!isControlOrMeta) {
  2207         DropDownToggleKey(aKeyEvent);
  2209       return NS_OK;
  2210 #endif
  2212     default: // printable key will be handled by keypress event.
  2213       incrementalSearchResetter.Cancel();
  2214       return NS_OK;
  2217   aKeyEvent->PreventDefault();
  2219   // Actually process the new index and let the selection code
  2220   // do the scrolling for us
  2221   PostHandleKeyEvent(newIndex, 0, keyEvent->IsShift(), isControlOrMeta);
  2222   return NS_OK;
  2225 nsresult
  2226 nsListControlFrame::KeyPress(nsIDOMEvent* aKeyEvent)
  2228   MOZ_ASSERT(aKeyEvent, "aKeyEvent is null.");
  2230   EventStates eventStates = mContent->AsElement()->State();
  2231   if (eventStates.HasState(NS_EVENT_STATE_DISABLED)) {
  2232     return NS_OK;
  2235   AutoIncrementalSearchResetter incrementalSearchResetter;
  2237   const WidgetKeyboardEvent* keyEvent =
  2238     aKeyEvent->GetInternalNSEvent()->AsKeyboardEvent();
  2239   MOZ_ASSERT(keyEvent,
  2240     "DOM event must have WidgetKeyboardEvent for its internal event");
  2242   // Select option with this as the first character
  2243   // XXX Not I18N compliant
  2245   // Don't do incremental search if the key event has already consumed.
  2246   if (keyEvent->mFlags.mDefaultPrevented) {
  2247     return NS_OK;
  2250   if (keyEvent->IsAlt()) {
  2251     return NS_OK;
  2254   // With some keyboard layout, space key causes non-ASCII space.
  2255   // So, the check in keydown event handler isn't enough, we need to check it
  2256   // again with keypress event.
  2257   if (keyEvent->charCode != ' ') {
  2258     mControlSelectMode = false;
  2261   bool isControlOrMeta = (keyEvent->IsControl() || keyEvent->IsMeta());
  2262   if (isControlOrMeta && keyEvent->charCode != ' ') {
  2263     return NS_OK;
  2266   // NOTE: If keyCode of keypress event is not 0, charCode is always 0.
  2267   //       Therefore, all non-printable keys are not handled after this block.
  2268   if (!keyEvent->charCode) {
  2269     // Backspace key will delete the last char in the string.  Otherwise,
  2270     // non-printable keypress should reset incremental search.
  2271     if (keyEvent->keyCode == NS_VK_BACK) {
  2272       incrementalSearchResetter.Cancel();
  2273       if (!GetIncrementalString().IsEmpty()) {
  2274         GetIncrementalString().Truncate(GetIncrementalString().Length() - 1);
  2276       aKeyEvent->PreventDefault();
  2277     } else {
  2278       // XXX When a select element has focus, even if the key causes nothing,
  2279       //     it might be better to call preventDefault() here because nobody
  2280       //     should expect one of other elements including chrome handles the
  2281       //     key event.
  2283     return NS_OK;
  2286   incrementalSearchResetter.Cancel();
  2288   // We ate the key if we got this far.
  2289   aKeyEvent->PreventDefault();
  2291   // XXX Why don't we check/modify timestamp first?
  2293   // Incremental Search: if time elapsed is below
  2294   // INCREMENTAL_SEARCH_KEYPRESS_TIME, append this keystroke to the search
  2295   // string we will use to find options and start searching at the current
  2296   // keystroke.  Otherwise, Truncate the string if it's been a long time
  2297   // since our last keypress.
  2298   if (keyEvent->time - gLastKeyTime > INCREMENTAL_SEARCH_KEYPRESS_TIME) {
  2299     // If this is ' ' and we are at the beginning of the string, treat it as
  2300     // "select this option" (bug 191543)
  2301     if (keyEvent->charCode == ' ') {
  2302       // Actually process the new index and let the selection code
  2303       // do the scrolling for us
  2304       PostHandleKeyEvent(mEndSelectionIndex, keyEvent->charCode,
  2305                          keyEvent->IsShift(), isControlOrMeta);
  2307       return NS_OK;
  2310     GetIncrementalString().Truncate();
  2313   gLastKeyTime = keyEvent->time;
  2315   // Append this keystroke to the search string. 
  2316   char16_t uniChar = ToLowerCase(static_cast<char16_t>(keyEvent->charCode));
  2317   GetIncrementalString().Append(uniChar);
  2319   // See bug 188199, if all letters in incremental string are same, just try to
  2320   // match the first one
  2321   nsAutoString incrementalString(GetIncrementalString());
  2322   uint32_t charIndex = 1, stringLength = incrementalString.Length();
  2323   while (charIndex < stringLength &&
  2324          incrementalString[charIndex] == incrementalString[charIndex - 1]) {
  2325     charIndex++;
  2327   if (charIndex == stringLength) {
  2328     incrementalString.Truncate(1);
  2329     stringLength = 1;
  2332   // Determine where we're going to start reading the string
  2333   // If we have multiple characters to look for, we start looking *at* the
  2334   // current option.  If we have only one character to look for, we start
  2335   // looking *after* the current option.	
  2336   // Exception: if there is no option selected to start at, we always start
  2337   // *at* 0.
  2338   int32_t startIndex = GetSelectedIndex();
  2339   if (startIndex == kNothingSelected) {
  2340     startIndex = 0;
  2341   } else if (stringLength == 1) {
  2342     startIndex++;
  2345   // now make sure there are options or we are wasting our time
  2346   nsRefPtr<dom::HTMLOptionsCollection> options = GetOptions();
  2347   NS_ENSURE_TRUE(options, NS_ERROR_FAILURE);
  2349   uint32_t numOptions = options->Length();
  2351   nsWeakFrame weakFrame(this);
  2352   for (uint32_t i = 0; i < numOptions; ++i) {
  2353     uint32_t index = (i + startIndex) % numOptions;
  2354     nsRefPtr<dom::HTMLOptionElement> optionElement =
  2355       options->ItemAsOption(index);
  2356     if (!optionElement || !optionElement->GetPrimaryFrame()) {
  2357       continue;
  2360     nsAutoString text;
  2361     if (NS_FAILED(optionElement->GetText(text)) ||
  2362         !StringBeginsWith(
  2363            nsContentUtils::TrimWhitespace<
  2364              nsContentUtils::IsHTMLWhitespaceOrNBSP>(text, false),
  2365            incrementalString, nsCaseInsensitiveStringComparator())) {
  2366       continue;
  2369     bool wasChanged = PerformSelection(index, keyEvent->IsShift(), isControlOrMeta);
  2370     if (!weakFrame.IsAlive()) {
  2371       return NS_OK;
  2373     if (!wasChanged) {
  2374       break;
  2377     // If UpdateSelection() returns false, that means the frame is no longer
  2378     // alive. We should stop doing anything.
  2379     if (!UpdateSelection()) {
  2380       return NS_OK;
  2382     break;
  2385   return NS_OK;
  2388 void
  2389 nsListControlFrame::PostHandleKeyEvent(int32_t aNewIndex,
  2390                                        uint32_t aCharCode,
  2391                                        bool aIsShift,
  2392                                        bool aIsControlOrMeta)
  2394   if (aNewIndex == kNothingSelected) {
  2395     return;
  2398   // If you hold control, but not shift, no key will actually do anything
  2399   // except space.
  2400   nsWeakFrame weakFrame(this);
  2401   bool wasChanged = false;
  2402   if (aIsControlOrMeta && !aIsShift && aCharCode != ' ') {
  2403     mStartSelectionIndex = aNewIndex;
  2404     mEndSelectionIndex = aNewIndex;
  2405     InvalidateFocus();
  2406     ScrollToIndex(aNewIndex);
  2407     if (!weakFrame.IsAlive()) {
  2408       return;
  2411 #ifdef ACCESSIBILITY
  2412     FireMenuItemActiveEvent();
  2413 #endif
  2414   } else if (mControlSelectMode && aCharCode == ' ') {
  2415     wasChanged = SingleSelection(aNewIndex, true);
  2416   } else {
  2417     wasChanged = PerformSelection(aNewIndex, aIsShift, aIsControlOrMeta);
  2419   if (wasChanged && weakFrame.IsAlive()) {
  2420     // dispatch event, update combobox, etc.
  2421     UpdateSelection();
  2426 /******************************************************************************
  2427  * nsListEventListener
  2428  *****************************************************************************/
  2430 NS_IMPL_ISUPPORTS(nsListEventListener, nsIDOMEventListener)
  2432 NS_IMETHODIMP
  2433 nsListEventListener::HandleEvent(nsIDOMEvent* aEvent)
  2435   if (!mFrame)
  2436     return NS_OK;
  2438   nsAutoString eventType;
  2439   aEvent->GetType(eventType);
  2440   if (eventType.EqualsLiteral("keydown"))
  2441     return mFrame->nsListControlFrame::KeyDown(aEvent);
  2442   if (eventType.EqualsLiteral("keypress"))
  2443     return mFrame->nsListControlFrame::KeyPress(aEvent);
  2444   if (eventType.EqualsLiteral("mousedown"))
  2445     return mFrame->nsListControlFrame::MouseDown(aEvent);
  2446   if (eventType.EqualsLiteral("mouseup"))
  2447     return mFrame->nsListControlFrame::MouseUp(aEvent);
  2448   if (eventType.EqualsLiteral("mousemove"))
  2449     return mFrame->nsListControlFrame::MouseMove(aEvent);
  2451   NS_ABORT();
  2452   return NS_OK;

mercurial