layout/forms/nsComboboxControlFrame.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/. */
     5 #include "nsCOMPtr.h"
     6 #include "nsComboboxControlFrame.h"
     7 #include "nsFocusManager.h"
     8 #include "nsFormControlFrame.h"
     9 #include "nsGkAtoms.h"
    10 #include "nsCSSAnonBoxes.h"
    11 #include "nsHTMLParts.h"
    12 #include "nsIFormControl.h"
    13 #include "nsNameSpaceManager.h"
    14 #include "nsIListControlFrame.h"
    15 #include "nsPIDOMWindow.h"
    16 #include "nsIPresShell.h"
    17 #include "nsContentList.h"
    18 #include "nsView.h"
    19 #include "nsViewManager.h"
    20 #include "nsIDOMEventListener.h"
    21 #include "nsIDOMNode.h"
    22 #include "nsISelectControlFrame.h"
    23 #include "nsContentUtils.h"
    24 #include "nsIDocument.h"
    25 #include "nsINodeInfo.h"
    26 #include "nsIScrollableFrame.h"
    27 #include "nsListControlFrame.h"
    28 #include "nsAutoPtr.h"
    29 #include "nsStyleSet.h"
    30 #include "nsNodeInfoManager.h"
    31 #include "nsContentCreatorFunctions.h"
    32 #include "nsLayoutUtils.h"
    33 #include "nsDisplayList.h"
    34 #include "nsITheme.h"
    35 #include "nsRenderingContext.h"
    36 #include "mozilla/Likely.h"
    37 #include <algorithm>
    38 #include "nsTextNode.h"
    39 #include "mozilla/AsyncEventDispatcher.h"
    40 #include "mozilla/EventStates.h"
    41 #include "mozilla/LookAndFeel.h"
    42 #include "mozilla/MouseEvents.h"
    43 #include "mozilla/unused.h"
    45 using namespace mozilla;
    47 NS_IMETHODIMP
    48 nsComboboxControlFrame::RedisplayTextEvent::Run()
    49 {
    50   if (mControlFrame)
    51     mControlFrame->HandleRedisplayTextEvent();
    52   return NS_OK;
    53 }
    55 class nsPresState;
    57 #define FIX_FOR_BUG_53259
    59 // Drop down list event management.
    60 // The combo box uses the following strategy for managing the drop-down list.
    61 // If the combo box or its arrow button is clicked on the drop-down list is displayed
    62 // If mouse exits the combo box with the drop-down list displayed the drop-down list
    63 // is asked to capture events
    64 // The drop-down list will capture all events including mouse down and up and will always
    65 // return with ListWasSelected method call regardless of whether an item in the list was
    66 // actually selected.
    67 // The ListWasSelected code will turn off mouse-capture for the drop-down list.
    68 // The drop-down list does not explicitly set capture when it is in the drop-down mode.
    71 /**
    72  * Helper class that listens to the combo boxes button. If the button is pressed the
    73  * combo box is toggled to open or close. this is used by Accessibility which presses
    74  * that button Programmatically.
    75  */
    76 class nsComboButtonListener : public nsIDOMEventListener
    77 {
    78 public:
    79   NS_DECL_ISUPPORTS
    81   NS_IMETHOD HandleEvent(nsIDOMEvent*) MOZ_OVERRIDE
    82   {
    83     mComboBox->ShowDropDown(!mComboBox->IsDroppedDown());
    84     return NS_OK;
    85   }
    87   nsComboButtonListener(nsComboboxControlFrame* aCombobox)
    88   {
    89     mComboBox = aCombobox;
    90   }
    92   virtual ~nsComboButtonListener() {}
    94   nsComboboxControlFrame* mComboBox;
    95 };
    97 NS_IMPL_ISUPPORTS(nsComboButtonListener,
    98                   nsIDOMEventListener)
   100 // static class data member for Bug 32920
   101 nsComboboxControlFrame* nsComboboxControlFrame::sFocused = nullptr;
   103 nsIFrame*
   104 NS_NewComboboxControlFrame(nsIPresShell* aPresShell, nsStyleContext* aContext, nsFrameState aStateFlags)
   105 {
   106   nsComboboxControlFrame* it = new (aPresShell) nsComboboxControlFrame(aContext);
   108   if (it) {
   109     // set the state flags (if any are provided)
   110     it->AddStateBits(aStateFlags);
   111   }
   113   return it;
   114 }
   116 NS_IMPL_FRAMEARENA_HELPERS(nsComboboxControlFrame)
   118 //-----------------------------------------------------------
   119 // Reflow Debugging Macros
   120 // These let us "see" how many reflow counts are happening
   121 //-----------------------------------------------------------
   122 #ifdef DO_REFLOW_COUNTER
   124 #define MAX_REFLOW_CNT 1024
   125 static int32_t gTotalReqs    = 0;;
   126 static int32_t gTotalReflows = 0;;
   127 static int32_t gReflowControlCntRQ[MAX_REFLOW_CNT];
   128 static int32_t gReflowControlCnt[MAX_REFLOW_CNT];
   129 static int32_t gReflowInx = -1;
   131 #define REFLOW_COUNTER() \
   132   if (mReflowId > -1) \
   133     gReflowControlCnt[mReflowId]++;
   135 #define REFLOW_COUNTER_REQUEST() \
   136   if (mReflowId > -1) \
   137     gReflowControlCntRQ[mReflowId]++;
   139 #define REFLOW_COUNTER_DUMP(__desc) \
   140   if (mReflowId > -1) {\
   141     gTotalReqs    += gReflowControlCntRQ[mReflowId];\
   142     gTotalReflows += gReflowControlCnt[mReflowId];\
   143     printf("** Id:%5d %s RF: %d RQ: %d   %d/%d  %5.2f\n", \
   144            mReflowId, (__desc), \
   145            gReflowControlCnt[mReflowId], \
   146            gReflowControlCntRQ[mReflowId],\
   147            gTotalReflows, gTotalReqs, float(gTotalReflows)/float(gTotalReqs)*100.0f);\
   148   }
   150 #define REFLOW_COUNTER_INIT() \
   151   if (gReflowInx < MAX_REFLOW_CNT) { \
   152     gReflowInx++; \
   153     mReflowId = gReflowInx; \
   154     gReflowControlCnt[mReflowId] = 0; \
   155     gReflowControlCntRQ[mReflowId] = 0; \
   156   } else { \
   157     mReflowId = -1; \
   158   }
   160 // reflow messages
   161 #define REFLOW_DEBUG_MSG(_msg1) printf((_msg1))
   162 #define REFLOW_DEBUG_MSG2(_msg1, _msg2) printf((_msg1), (_msg2))
   163 #define REFLOW_DEBUG_MSG3(_msg1, _msg2, _msg3) printf((_msg1), (_msg2), (_msg3))
   164 #define REFLOW_DEBUG_MSG4(_msg1, _msg2, _msg3, _msg4) printf((_msg1), (_msg2), (_msg3), (_msg4))
   166 #else //-------------
   168 #define REFLOW_COUNTER_REQUEST()
   169 #define REFLOW_COUNTER()
   170 #define REFLOW_COUNTER_DUMP(__desc)
   171 #define REFLOW_COUNTER_INIT()
   173 #define REFLOW_DEBUG_MSG(_msg)
   174 #define REFLOW_DEBUG_MSG2(_msg1, _msg2)
   175 #define REFLOW_DEBUG_MSG3(_msg1, _msg2, _msg3)
   176 #define REFLOW_DEBUG_MSG4(_msg1, _msg2, _msg3, _msg4)
   179 #endif
   181 //------------------------------------------
   182 // This is for being VERY noisy
   183 //------------------------------------------
   184 #ifdef DO_VERY_NOISY
   185 #define REFLOW_NOISY_MSG(_msg1) printf((_msg1))
   186 #define REFLOW_NOISY_MSG2(_msg1, _msg2) printf((_msg1), (_msg2))
   187 #define REFLOW_NOISY_MSG3(_msg1, _msg2, _msg3) printf((_msg1), (_msg2), (_msg3))
   188 #define REFLOW_NOISY_MSG4(_msg1, _msg2, _msg3, _msg4) printf((_msg1), (_msg2), (_msg3), (_msg4))
   189 #else
   190 #define REFLOW_NOISY_MSG(_msg)
   191 #define REFLOW_NOISY_MSG2(_msg1, _msg2)
   192 #define REFLOW_NOISY_MSG3(_msg1, _msg2, _msg3)
   193 #define REFLOW_NOISY_MSG4(_msg1, _msg2, _msg3, _msg4)
   194 #endif
   196 //------------------------------------------
   197 // Displays value in pixels or twips
   198 //------------------------------------------
   199 #ifdef DO_PIXELS
   200 #define PX(__v) __v / 15
   201 #else
   202 #define PX(__v) __v
   203 #endif
   205 //------------------------------------------------------
   206 //-- Done with macros
   207 //------------------------------------------------------
   209 nsComboboxControlFrame::nsComboboxControlFrame(nsStyleContext* aContext)
   210   : nsBlockFrame(aContext)
   211   , mDisplayFrame(nullptr)
   212   , mButtonFrame(nullptr)
   213   , mDropdownFrame(nullptr)
   214   , mListControlFrame(nullptr)
   215   , mDisplayWidth(0)
   216   , mRecentSelectedIndex(NS_SKIP_NOTIFY_INDEX)
   217   , mDisplayedIndex(-1)
   218   , mLastDropDownAboveScreenY(nscoord_MIN)
   219   , mLastDropDownBelowScreenY(nscoord_MIN)
   220   , mDroppedDown(false)
   221   , mInRedisplayText(false)
   222   , mDelayedShowDropDown(false)
   223 {
   224   REFLOW_COUNTER_INIT()
   225 }
   227 //--------------------------------------------------------------
   228 nsComboboxControlFrame::~nsComboboxControlFrame()
   229 {
   230   REFLOW_COUNTER_DUMP("nsCCF");
   231 }
   233 //--------------------------------------------------------------
   235 NS_QUERYFRAME_HEAD(nsComboboxControlFrame)
   236   NS_QUERYFRAME_ENTRY(nsIComboboxControlFrame)
   237   NS_QUERYFRAME_ENTRY(nsIFormControlFrame)
   238   NS_QUERYFRAME_ENTRY(nsIAnonymousContentCreator)
   239   NS_QUERYFRAME_ENTRY(nsISelectControlFrame)
   240   NS_QUERYFRAME_ENTRY(nsIStatefulFrame)
   241 NS_QUERYFRAME_TAIL_INHERITING(nsBlockFrame)
   243 #ifdef ACCESSIBILITY
   244 a11y::AccType
   245 nsComboboxControlFrame::AccessibleType()
   246 {
   247   return a11y::eHTMLComboboxType;
   248 }
   249 #endif
   251 void
   252 nsComboboxControlFrame::SetFocus(bool aOn, bool aRepaint)
   253 {
   254   nsWeakFrame weakFrame(this);
   255   if (aOn) {
   256     nsListControlFrame::ComboboxFocusSet();
   257     sFocused = this;
   258     if (mDelayedShowDropDown) {
   259       ShowDropDown(true); // might destroy us
   260       if (!weakFrame.IsAlive()) {
   261         return;
   262       }
   263     }
   264   } else {
   265     sFocused = nullptr;
   266     mDelayedShowDropDown = false;
   267     if (mDroppedDown) {
   268       mListControlFrame->ComboboxFinish(mDisplayedIndex); // might destroy us
   269       if (!weakFrame.IsAlive()) {
   270         return;
   271       }
   272     }
   273     // May delete |this|.
   274     mListControlFrame->FireOnChange();
   275   }
   277   if (!weakFrame.IsAlive()) {
   278     return;
   279   }
   281   // This is needed on a temporary basis. It causes the focus
   282   // rect to be drawn. This is much faster than ReResolvingStyle
   283   // Bug 32920
   284   InvalidateFrame();
   285 }
   287 void
   288 nsComboboxControlFrame::ShowPopup(bool aShowPopup)
   289 {
   290   nsView* view = mDropdownFrame->GetView();
   291   nsViewManager* viewManager = view->GetViewManager();
   293   if (aShowPopup) {
   294     nsRect rect = mDropdownFrame->GetRect();
   295     rect.x = rect.y = 0;
   296     viewManager->ResizeView(view, rect);
   297     viewManager->SetViewVisibility(view, nsViewVisibility_kShow);
   298   } else {
   299     viewManager->SetViewVisibility(view, nsViewVisibility_kHide);
   300     nsRect emptyRect(0, 0, 0, 0);
   301     viewManager->ResizeView(view, emptyRect);
   302   }
   304   // fire a popup dom event
   305   nsEventStatus status = nsEventStatus_eIgnore;
   306   WidgetMouseEvent event(true, aShowPopup ?
   307                          NS_XUL_POPUP_SHOWING : NS_XUL_POPUP_HIDING, nullptr,
   308                          WidgetMouseEvent::eReal);
   310   nsCOMPtr<nsIPresShell> shell = PresContext()->GetPresShell();
   311   if (shell)
   312     shell->HandleDOMEventWithTarget(mContent, &event, &status);
   313 }
   315 bool
   316 nsComboboxControlFrame::ShowList(bool aShowList)
   317 {
   318   nsView* view = mDropdownFrame->GetView();
   319   if (aShowList) {
   320     NS_ASSERTION(!view->HasWidget(),
   321                  "We shouldn't have a widget before we need to display the popup");
   323     // Create the widget for the drop-down list
   324     view->GetViewManager()->SetViewFloating(view, true);
   326     nsWidgetInitData widgetData;
   327     widgetData.mWindowType  = eWindowType_popup;
   328     widgetData.mBorderStyle = eBorderStyle_default;
   329     view->CreateWidgetForPopup(&widgetData);
   330   } else {
   331     nsIWidget* widget = view->GetWidget();
   332     if (widget) {
   333       // We must do this before ShowPopup in case it destroys us (bug 813442).
   334       widget->CaptureRollupEvents(this, false);
   335     }
   336   }
   338   nsWeakFrame weakFrame(this);
   339   ShowPopup(aShowList);  // might destroy us
   340   if (!weakFrame.IsAlive()) {
   341     return false;
   342   }
   344   mDroppedDown = aShowList;
   345   nsIWidget* widget = view->GetWidget();
   346   if (mDroppedDown) {
   347     // The listcontrol frame will call back to the nsComboboxControlFrame's
   348     // ListWasSelected which will stop the capture.
   349     mListControlFrame->AboutToDropDown();
   350     mListControlFrame->CaptureMouseEvents(true);
   351     if (widget) {
   352       widget->CaptureRollupEvents(this, true);
   353     }
   354   } else {
   355     if (widget) {
   356       view->DestroyWidget();
   357     }
   358   }
   360   return weakFrame.IsAlive();
   361 }
   363 class nsResizeDropdownAtFinalPosition
   364   : public nsIReflowCallback, public nsRunnable
   365 {
   366 public:
   367   nsResizeDropdownAtFinalPosition(nsComboboxControlFrame* aFrame)
   368     : mFrame(aFrame)
   369   {
   370     MOZ_COUNT_CTOR(nsResizeDropdownAtFinalPosition);
   371   }
   372   ~nsResizeDropdownAtFinalPosition()
   373   {
   374     MOZ_COUNT_DTOR(nsResizeDropdownAtFinalPosition);
   375   }
   377   virtual bool ReflowFinished() MOZ_OVERRIDE
   378   {
   379     Run();
   380     NS_RELEASE_THIS();
   381     return false;
   382   }
   384   virtual void ReflowCallbackCanceled() MOZ_OVERRIDE
   385   {
   386     NS_RELEASE_THIS();
   387   }
   389   NS_IMETHODIMP Run()
   390   {
   391     if (mFrame.IsAlive()) {
   392       static_cast<nsComboboxControlFrame*>(mFrame.GetFrame())->
   393         AbsolutelyPositionDropDown();
   394     }
   395     return NS_OK;
   396   }
   398   nsWeakFrame mFrame;
   399 };
   401 nsresult
   402 nsComboboxControlFrame::ReflowDropdown(nsPresContext*  aPresContext,
   403                                        const nsHTMLReflowState& aReflowState)
   404 {
   405   // All we want out of it later on, really, is the height of a row, so we
   406   // don't even need to cache mDropdownFrame's ascent or anything.  If we don't
   407   // need to reflow it, just bail out here.
   408   if (!aReflowState.ShouldReflowAllKids() &&
   409       !NS_SUBTREE_DIRTY(mDropdownFrame)) {
   410     return NS_OK;
   411   }
   413   // XXXbz this will, for small-height dropdowns, have extra space on the right
   414   // edge for the scrollbar we don't show... but that's the best we can do here
   415   // for now.
   416   nsSize availSize(aReflowState.AvailableWidth(), NS_UNCONSTRAINEDSIZE);
   417   nsHTMLReflowState kidReflowState(aPresContext, aReflowState, mDropdownFrame,
   418                                    availSize);
   420   // If the dropdown's intrinsic width is narrower than our specified width,
   421   // then expand it out.  We want our border-box width to end up the same as
   422   // the dropdown's so account for both sets of mComputedBorderPadding.
   423   nscoord forcedWidth = aReflowState.ComputedWidth() +
   424     aReflowState.ComputedPhysicalBorderPadding().LeftRight() -
   425     kidReflowState.ComputedPhysicalBorderPadding().LeftRight();
   426   kidReflowState.SetComputedWidth(std::max(kidReflowState.ComputedWidth(),
   427                                          forcedWidth));
   429   // ensure we start off hidden
   430   if (GetStateBits() & NS_FRAME_FIRST_REFLOW) {
   431     nsView* view = mDropdownFrame->GetView();
   432     nsViewManager* viewManager = view->GetViewManager();
   433     viewManager->SetViewVisibility(view, nsViewVisibility_kHide);
   434     nsRect emptyRect(0, 0, 0, 0);
   435     viewManager->ResizeView(view, emptyRect);
   436   }
   438   // Allow the child to move/size/change-visibility its view if it's currently
   439   // dropped down
   440   int32_t flags = NS_FRAME_NO_MOVE_FRAME | NS_FRAME_NO_VISIBILITY | NS_FRAME_NO_SIZE_VIEW;
   441   if (mDroppedDown) {
   442     flags = 0;
   443   }
   444   nsRect rect = mDropdownFrame->GetRect();
   445   nsHTMLReflowMetrics desiredSize(aReflowState);
   446   nsReflowStatus ignoredStatus;
   447   nsresult rv = ReflowChild(mDropdownFrame, aPresContext, desiredSize,
   448                             kidReflowState, rect.x, rect.y, flags,
   449                             ignoredStatus);
   451    // Set the child's width and height to its desired size
   452   FinishReflowChild(mDropdownFrame, aPresContext, desiredSize,
   453                     &kidReflowState, rect.x, rect.y, flags);
   454   return rv;
   455 }
   457 nsPoint
   458 nsComboboxControlFrame::GetCSSTransformTranslation()
   459 {
   460   nsIFrame* frame = this;
   461   bool is3DTransform = false;
   462   gfxMatrix transform;
   463   while (frame) {
   464     nsIFrame* parent;
   465     gfx3DMatrix ctm = frame->GetTransformMatrix(nullptr, &parent);
   466     gfxMatrix matrix;
   467     if (ctm.Is2D(&matrix)) {
   468       transform = transform * matrix;
   469     } else {
   470       is3DTransform = true;
   471       break;
   472     }
   473     frame = parent;
   474   }
   475   nsPoint translation;
   476   if (!is3DTransform && !transform.HasNonTranslation()) {
   477     nsPresContext* pc = PresContext();
   478     gfxPoint pixelTranslation = transform.GetTranslation();
   479     int32_t apd = pc->AppUnitsPerDevPixel();
   480     translation.x = NSFloatPixelsToAppUnits(float(pixelTranslation.x), apd);
   481     translation.y = NSFloatPixelsToAppUnits(float(pixelTranslation.y), apd);
   482     // To get the translation introduced only by transforms we subtract the
   483     // regular non-transform translation.
   484     nsRootPresContext* rootPC = pc->GetRootPresContext();
   485     if (rootPC) {
   486       translation -= GetOffsetToCrossDoc(rootPC->PresShell()->GetRootFrame());
   487     } else {
   488       translation.x = translation.y = 0;
   489     }
   490   }
   491   return translation;
   492 }
   494 class nsAsyncRollup : public nsRunnable
   495 {
   496 public:
   497   nsAsyncRollup(nsComboboxControlFrame* aFrame) : mFrame(aFrame) {}
   498   NS_IMETHODIMP Run()
   499   {
   500     if (mFrame.IsAlive()) {
   501       static_cast<nsComboboxControlFrame*>(mFrame.GetFrame())
   502         ->RollupFromList();
   503     }
   504     return NS_OK;
   505   }
   506   nsWeakFrame mFrame;
   507 };
   509 class nsAsyncResize : public nsRunnable
   510 {
   511 public:
   512   nsAsyncResize(nsComboboxControlFrame* aFrame) : mFrame(aFrame) {}
   513   NS_IMETHODIMP Run()
   514   {
   515     if (mFrame.IsAlive()) {
   516       nsComboboxControlFrame* combo =
   517         static_cast<nsComboboxControlFrame*>(mFrame.GetFrame());
   518       static_cast<nsListControlFrame*>(combo->mDropdownFrame)->
   519         SetSuppressScrollbarUpdate(true);
   520       nsCOMPtr<nsIPresShell> shell = mFrame->PresContext()->PresShell();
   521       shell->FrameNeedsReflow(combo->mDropdownFrame, nsIPresShell::eResize,
   522                               NS_FRAME_IS_DIRTY);
   523       shell->FlushPendingNotifications(Flush_Layout);
   524       if (mFrame.IsAlive()) {
   525         combo = static_cast<nsComboboxControlFrame*>(mFrame.GetFrame());
   526         static_cast<nsListControlFrame*>(combo->mDropdownFrame)->
   527           SetSuppressScrollbarUpdate(false);
   528         if (combo->mDelayedShowDropDown) {
   529           combo->ShowDropDown(true);
   530         }
   531       }
   532     }
   533     return NS_OK;
   534   }
   535   nsWeakFrame mFrame;
   536 };
   538 void
   539 nsComboboxControlFrame::GetAvailableDropdownSpace(nscoord* aAbove,
   540                                                   nscoord* aBelow,
   541                                                   nsPoint* aTranslation)
   542 {
   543   // Note: At first glance, it appears that you could simply get the absolute
   544   // bounding box for the dropdown list by first getting its view, then getting
   545   // the view's nsIWidget, then asking the nsIWidget for its AbsoluteBounds.
   546   // The problem with this approach, is that the dropdown lists y location can
   547   // change based on whether the dropdown is placed below or above the display
   548   // frame.  The approach, taken here is to get the absolute position of the
   549   // display frame and use its location to determine if the dropdown will go
   550   // offscreen.
   552   // Normal frame geometry (eg GetOffsetTo, mRect) doesn't include transforms.
   553   // In the special case that our transform is only a 2D translation we
   554   // introduce this hack so that the dropdown will show up in the right place.
   555   *aTranslation = GetCSSTransformTranslation();
   556   *aAbove = 0;
   557   *aBelow = 0;
   559   nsRect screen = nsFormControlFrame::GetUsableScreenRect(PresContext());
   560   if (mLastDropDownBelowScreenY == nscoord_MIN) {
   561     nsRect thisScreenRect = GetScreenRectInAppUnits();
   562     mLastDropDownBelowScreenY = thisScreenRect.YMost() + aTranslation->y;
   563     mLastDropDownAboveScreenY = thisScreenRect.y + aTranslation->y;
   564   }
   566   nscoord minY;
   567   nsPresContext* pc = PresContext()->GetToplevelContentDocumentPresContext();
   568   nsIFrame* root = pc ? pc->PresShell()->GetRootFrame() : nullptr;
   569   if (root) {
   570     minY = root->GetScreenRectInAppUnits().y;
   571     if (mLastDropDownBelowScreenY < minY) {
   572       // Don't allow the drop-down to be placed above the content area.
   573       return;
   574     }
   575   } else {
   576     minY = screen.y;
   577   }
   579   nscoord below = screen.YMost() - mLastDropDownBelowScreenY;
   580   nscoord above = mLastDropDownAboveScreenY - minY;
   582   // If the difference between the space above and below is less
   583   // than a row-height, then we favor the space below.
   584   if (above >= below) {
   585     nsListControlFrame* lcf = static_cast<nsListControlFrame*>(mDropdownFrame);
   586     nscoord rowHeight = lcf->GetHeightOfARow();
   587     if (above < below + rowHeight) {
   588       above -= rowHeight;
   589     }
   590   }
   592   *aBelow = below;
   593   *aAbove = above;
   594 }
   596 nsComboboxControlFrame::DropDownPositionState
   597 nsComboboxControlFrame::AbsolutelyPositionDropDown()
   598 {
   599   nsPoint translation;
   600   nscoord above, below;
   601   mLastDropDownBelowScreenY = nscoord_MIN;
   602   GetAvailableDropdownSpace(&above, &below, &translation);
   603   if (above <= 0 && below <= 0) {
   604     if (IsDroppedDown()) {
   605       // Hide the view immediately to minimize flicker.
   606       nsView* view = mDropdownFrame->GetView();
   607       view->GetViewManager()->SetViewVisibility(view, nsViewVisibility_kHide);
   608       NS_DispatchToCurrentThread(new nsAsyncRollup(this));
   609     }
   610     return eDropDownPositionSuppressed;
   611   }
   613   nsSize dropdownSize = mDropdownFrame->GetSize();
   614   nscoord height = std::max(above, below);
   615   nsListControlFrame* lcf = static_cast<nsListControlFrame*>(mDropdownFrame);
   616   if (height < dropdownSize.height) {
   617     if (lcf->GetNumDisplayRows() > 1) {
   618       // The drop-down doesn't fit and currently shows more than 1 row -
   619       // schedule a resize to show fewer rows.
   620       NS_DispatchToCurrentThread(new nsAsyncResize(this));
   621       return eDropDownPositionPendingResize;
   622     }
   623   } else if (height > (dropdownSize.height + lcf->GetHeightOfARow() * 1.5) &&
   624              lcf->GetDropdownCanGrow()) {
   625     // The drop-down fits but there is room for at least 1.5 more rows -
   626     // schedule a resize to show more rows if it has more rows to show.
   627     // (1.5 rows for good measure to avoid any rounding issues that would
   628     // lead to a loop of reflow requests)
   629     NS_DispatchToCurrentThread(new nsAsyncResize(this));
   630     return eDropDownPositionPendingResize;
   631   }
   633   // Position the drop-down below if there is room, otherwise place it above
   634   // if there is room.  If there is no room for it on either side then place
   635   // it below (to avoid overlapping UI like the URL bar).
   636   bool b = dropdownSize.height <= below || dropdownSize.height > above;
   637   nsPoint dropdownPosition(0, b ? GetRect().height : -dropdownSize.height);
   638   if (StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL) {
   639     // Align the right edge of the drop-down with the right edge of the control.
   640     dropdownPosition.x = GetRect().width - dropdownSize.width;
   641   }
   643   // Don't position the view unless the position changed since it might cause
   644   // a call to NotifyGeometryChange() and an infinite loop here.
   645   const nsPoint currentPos = mDropdownFrame->GetPosition();
   646   const nsPoint newPos = dropdownPosition + translation;
   647   if (currentPos != newPos) {
   648     mDropdownFrame->SetPosition(newPos);
   649     nsContainerFrame::PositionFrameView(mDropdownFrame);
   650   }
   651   return eDropDownPositionFinal;
   652 }
   654 void
   655 nsComboboxControlFrame::NotifyGeometryChange()
   656 {
   657   // We don't need to resize if we're not dropped down since ShowDropDown
   658   // does that, or if we're dirty then the reflow callback does it,
   659   // or if we have a delayed ShowDropDown pending.
   660   if (IsDroppedDown() &&
   661       !(GetStateBits() & NS_FRAME_IS_DIRTY) &&
   662       !mDelayedShowDropDown) {
   663     // Async because we're likely in a middle of a scroll here so
   664     // frame/view positions are in flux.
   665     nsRefPtr<nsResizeDropdownAtFinalPosition> resize =
   666       new nsResizeDropdownAtFinalPosition(this);
   667     NS_DispatchToCurrentThread(resize);
   668   }
   669 }
   671 //----------------------------------------------------------
   672 //
   673 //----------------------------------------------------------
   674 #ifdef DO_REFLOW_DEBUG
   675 static int myCounter = 0;
   677 static void printSize(char * aDesc, nscoord aSize)
   678 {
   679   printf(" %s: ", aDesc);
   680   if (aSize == NS_UNCONSTRAINEDSIZE) {
   681     printf("UC");
   682   } else {
   683     printf("%d", PX(aSize));
   684   }
   685 }
   686 #endif
   688 //-------------------------------------------------------------------
   689 //-- Main Reflow for the Combobox
   690 //-------------------------------------------------------------------
   692 nscoord
   693 nsComboboxControlFrame::GetIntrinsicWidth(nsRenderingContext* aRenderingContext,
   694                                           nsLayoutUtils::IntrinsicWidthType aType)
   695 {
   696   // get the scrollbar width, we'll use this later
   697   nscoord scrollbarWidth = 0;
   698   nsPresContext* presContext = PresContext();
   699   if (mListControlFrame) {
   700     nsIScrollableFrame* scrollable = do_QueryFrame(mListControlFrame);
   701     NS_ASSERTION(scrollable, "List must be a scrollable frame");
   702     scrollbarWidth = scrollable->GetNondisappearingScrollbarWidth(
   703       presContext, aRenderingContext);
   704   }
   706   nscoord displayWidth = 0;
   707   if (MOZ_LIKELY(mDisplayFrame)) {
   708     displayWidth = nsLayoutUtils::IntrinsicForContainer(aRenderingContext,
   709                                                         mDisplayFrame,
   710                                                         aType);
   711   }
   713   if (mDropdownFrame) {
   714     nscoord dropdownContentWidth;
   715     bool isUsingOverlayScrollbars =
   716       LookAndFeel::GetInt(LookAndFeel::eIntID_UseOverlayScrollbars) != 0;
   717     if (aType == nsLayoutUtils::MIN_WIDTH) {
   718       dropdownContentWidth = mDropdownFrame->GetMinWidth(aRenderingContext);
   719       if (isUsingOverlayScrollbars) {
   720         dropdownContentWidth += scrollbarWidth;
   721       }
   722     } else {
   723       NS_ASSERTION(aType == nsLayoutUtils::PREF_WIDTH, "Unexpected type");
   724       dropdownContentWidth = mDropdownFrame->GetPrefWidth(aRenderingContext);
   725       if (isUsingOverlayScrollbars) {
   726         dropdownContentWidth += scrollbarWidth;
   727       }
   728     }
   729     dropdownContentWidth = NSCoordSaturatingSubtract(dropdownContentWidth,
   730                                                      scrollbarWidth,
   731                                                      nscoord_MAX);
   733     displayWidth = std::max(dropdownContentWidth, displayWidth);
   734   }
   736   // add room for the dropmarker button if there is one
   737   if (!IsThemed() || presContext->GetTheme()->ThemeNeedsComboboxDropmarker())
   738     displayWidth += scrollbarWidth;
   740   return displayWidth;
   742 }
   744 nscoord
   745 nsComboboxControlFrame::GetMinWidth(nsRenderingContext *aRenderingContext)
   746 {
   747   nscoord minWidth;
   748   DISPLAY_MIN_WIDTH(this, minWidth);
   749   minWidth = GetIntrinsicWidth(aRenderingContext, nsLayoutUtils::MIN_WIDTH);
   750   return minWidth;
   751 }
   753 nscoord
   754 nsComboboxControlFrame::GetPrefWidth(nsRenderingContext *aRenderingContext)
   755 {
   756   nscoord prefWidth;
   757   DISPLAY_PREF_WIDTH(this, prefWidth);
   758   prefWidth = GetIntrinsicWidth(aRenderingContext, nsLayoutUtils::PREF_WIDTH);
   759   return prefWidth;
   760 }
   762 nsresult
   763 nsComboboxControlFrame::Reflow(nsPresContext*          aPresContext,
   764                                nsHTMLReflowMetrics&     aDesiredSize,
   765                                const nsHTMLReflowState& aReflowState,
   766                                nsReflowStatus&          aStatus)
   767 {
   768   // Constraints we try to satisfy:
   770   // 1) Default width of button is the vertical scrollbar size
   771   // 2) If the width of button is bigger than our width, set width of
   772   //    button to 0.
   773   // 3) Default height of button is height of display area
   774   // 4) Width of display area is whatever is left over from our width after
   775   //    allocating width for the button.
   776   // 5) Height of display area is GetHeightOfARow() on the
   777   //    mListControlFrame.
   779   if (!mDisplayFrame || !mButtonFrame || !mDropdownFrame) {
   780     NS_ERROR("Why did the frame constructor allow this to happen?  Fix it!!");
   781     return NS_ERROR_UNEXPECTED;
   782   }
   784   // Make sure the displayed text is the same as the selected option, bug 297389.
   785   int32_t selectedIndex;
   786   nsAutoString selectedOptionText;
   787   if (!mDroppedDown) {
   788     selectedIndex = mListControlFrame->GetSelectedIndex();
   789   }
   790   else {
   791     // In dropped down mode the "selected index" is the hovered menu item,
   792     // we want the last selected item which is |mDisplayedIndex| in this case.
   793     selectedIndex = mDisplayedIndex;
   794   }
   795   if (selectedIndex != -1) {
   796     mListControlFrame->GetOptionText(selectedIndex, selectedOptionText);
   797   }
   798   if (mDisplayedOptionText != selectedOptionText) {
   799     RedisplayText(selectedIndex);
   800   }
   802   // First reflow our dropdown so that we know how tall we should be.
   803   ReflowDropdown(aPresContext, aReflowState);
   804   nsRefPtr<nsResizeDropdownAtFinalPosition> resize =
   805     new nsResizeDropdownAtFinalPosition(this);
   806   if (NS_SUCCEEDED(aPresContext->PresShell()->PostReflowCallback(resize))) {
   807     // The reflow callback queue doesn't AddRef so we keep it alive until
   808     // it's released in its ReflowFinished / ReflowCallbackCanceled.
   809     unused << resize.forget();
   810   }
   812   // Get the width of the vertical scrollbar.  That will be the width of the
   813   // dropdown button.
   814   nscoord buttonWidth;
   815   const nsStyleDisplay *disp = StyleDisplay();
   816   if (IsThemed(disp) && !aPresContext->GetTheme()->ThemeNeedsComboboxDropmarker()) {
   817     buttonWidth = 0;
   818   }
   819   else {
   820     nsIScrollableFrame* scrollable = do_QueryFrame(mListControlFrame);
   821     NS_ASSERTION(scrollable, "List must be a scrollable frame");
   822     buttonWidth = scrollable->GetNondisappearingScrollbarWidth(
   823       PresContext(), aReflowState.rendContext);
   824     if (buttonWidth > aReflowState.ComputedWidth()) {
   825       buttonWidth = 0;
   826     }
   827   }
   829   mDisplayWidth = aReflowState.ComputedWidth() - buttonWidth;
   831   nsresult rv = nsBlockFrame::Reflow(aPresContext, aDesiredSize, aReflowState,
   832                                     aStatus);
   833   NS_ENSURE_SUCCESS(rv, rv);
   835   // The button should occupy the same space as a scrollbar
   836   nsRect buttonRect = mButtonFrame->GetRect();
   838   if (StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL) {
   839     buttonRect.x = aReflowState.ComputedPhysicalBorderPadding().left -
   840                    aReflowState.ComputedPhysicalPadding().left;
   841   }
   842   else {
   843     buttonRect.x = aReflowState.ComputedPhysicalBorderPadding().LeftRight() +
   844                    mDisplayWidth -
   845                    (aReflowState.ComputedPhysicalBorderPadding().right -
   846                     aReflowState.ComputedPhysicalPadding().right);
   847   }
   848   buttonRect.width = buttonWidth;
   850   buttonRect.y = this->GetUsedBorder().top;
   851   buttonRect.height = mDisplayFrame->GetRect().height +
   852                       this->GetUsedPadding().TopBottom();
   854   mButtonFrame->SetRect(buttonRect);
   856   if (!NS_INLINE_IS_BREAK_BEFORE(aStatus) &&
   857       !NS_FRAME_IS_FULLY_COMPLETE(aStatus)) {
   858     // This frame didn't fit inside a fragmentation container.  Splitting
   859     // a nsComboboxControlFrame makes no sense, so we override the status here.
   860     aStatus = NS_FRAME_COMPLETE;
   861   }
   862   return rv;
   863 }
   865 //--------------------------------------------------------------
   867 nsIAtom*
   868 nsComboboxControlFrame::GetType() const
   869 {
   870   return nsGkAtoms::comboboxControlFrame;
   871 }
   873 #ifdef DEBUG_FRAME_DUMP
   874 nsresult
   875 nsComboboxControlFrame::GetFrameName(nsAString& aResult) const
   876 {
   877   return MakeFrameName(NS_LITERAL_STRING("ComboboxControl"), aResult);
   878 }
   879 #endif
   882 //----------------------------------------------------------------------
   883 // nsIComboboxControlFrame
   884 //----------------------------------------------------------------------
   885 void
   886 nsComboboxControlFrame::ShowDropDown(bool aDoDropDown)
   887 {
   888   mDelayedShowDropDown = false;
   889   EventStates eventStates = mContent->AsElement()->State();
   890   if (aDoDropDown && eventStates.HasState(NS_EVENT_STATE_DISABLED)) {
   891     return;
   892   }
   894   if (!mDroppedDown && aDoDropDown) {
   895     nsFocusManager* fm = nsFocusManager::GetFocusManager();
   896     if (!fm || fm->GetFocusedContent() == GetContent()) {
   897       DropDownPositionState state = AbsolutelyPositionDropDown();
   898       if (state == eDropDownPositionFinal) {
   899         ShowList(aDoDropDown); // might destroy us
   900       } else if (state == eDropDownPositionPendingResize) {
   901         // Delay until after the resize reflow, see nsAsyncResize.
   902         mDelayedShowDropDown = true;
   903       }
   904     } else {
   905       // Delay until we get focus, see SetFocus().
   906       mDelayedShowDropDown = true;
   907     }
   908   } else if (mDroppedDown && !aDoDropDown) {
   909     ShowList(aDoDropDown); // might destroy us
   910   }
   911 }
   913 void
   914 nsComboboxControlFrame::SetDropDown(nsIFrame* aDropDownFrame)
   915 {
   916   mDropdownFrame = aDropDownFrame;
   917   mListControlFrame = do_QueryFrame(mDropdownFrame);
   918 }
   920 nsIFrame*
   921 nsComboboxControlFrame::GetDropDown()
   922 {
   923   return mDropdownFrame;
   924 }
   926 ///////////////////////////////////////////////////////////////
   928 NS_IMETHODIMP
   929 nsComboboxControlFrame::RedisplaySelectedText()
   930 {
   931   nsAutoScriptBlocker scriptBlocker;
   932   return RedisplayText(mListControlFrame->GetSelectedIndex());
   933 }
   935 nsresult
   936 nsComboboxControlFrame::RedisplayText(int32_t aIndex)
   937 {
   938   // Get the text to display
   939   if (aIndex != -1) {
   940     mListControlFrame->GetOptionText(aIndex, mDisplayedOptionText);
   941   } else {
   942     mDisplayedOptionText.Truncate();
   943   }
   944   mDisplayedIndex = aIndex;
   946   REFLOW_DEBUG_MSG2("RedisplayText \"%s\"\n",
   947                     NS_LossyConvertUTF16toASCII(mDisplayedOptionText).get());
   949   // Send reflow command because the new text maybe larger
   950   nsresult rv = NS_OK;
   951   if (mDisplayContent) {
   952     // Don't call ActuallyDisplayText(true) directly here since that
   953     // could cause recursive frame construction. See bug 283117 and the comment in
   954     // HandleRedisplayTextEvent() below.
   956     // Revoke outstanding events to avoid out-of-order events which could mean
   957     // displaying the wrong text.
   958     mRedisplayTextEvent.Revoke();
   960     NS_ASSERTION(!nsContentUtils::IsSafeToRunScript(),
   961                  "If we happen to run our redisplay event now, we might kill "
   962                  "ourselves!");
   964     nsRefPtr<RedisplayTextEvent> event = new RedisplayTextEvent(this);
   965     mRedisplayTextEvent = event;
   966     if (!nsContentUtils::AddScriptRunner(event))
   967       mRedisplayTextEvent.Forget();
   968   }
   969   return rv;
   970 }
   972 void
   973 nsComboboxControlFrame::HandleRedisplayTextEvent()
   974 {
   975   // First, make sure that the content model is up to date and we've
   976   // constructed the frames for all our content in the right places.
   977   // Otherwise they'll end up under the wrong insertion frame when we
   978   // ActuallyDisplayText, since that flushes out the content sink by
   979   // calling SetText on a DOM node with aNotify set to true.  See bug
   980   // 289730.
   981   nsWeakFrame weakThis(this);
   982   PresContext()->Document()->
   983     FlushPendingNotifications(Flush_ContentAndNotify);
   984   if (!weakThis.IsAlive())
   985     return;
   987   // Redirect frame insertions during this method (see GetContentInsertionFrame())
   988   // so that any reframing that the frame constructor forces upon us is inserted
   989   // into the correct parent (mDisplayFrame). See bug 282607.
   990   NS_PRECONDITION(!mInRedisplayText, "Nested RedisplayText");
   991   mInRedisplayText = true;
   992   mRedisplayTextEvent.Forget();
   994   ActuallyDisplayText(true);
   995   // XXXbz This should perhaps be eResize.  Check.
   996   PresContext()->PresShell()->FrameNeedsReflow(mDisplayFrame,
   997                                                nsIPresShell::eStyleChange,
   998                                                NS_FRAME_IS_DIRTY);
  1000   mInRedisplayText = false;
  1003 void
  1004 nsComboboxControlFrame::ActuallyDisplayText(bool aNotify)
  1006   if (mDisplayedOptionText.IsEmpty()) {
  1007     // Have to use a non-breaking space for line-height calculations
  1008     // to be right
  1009     static const char16_t space = 0xA0;
  1010     mDisplayContent->SetText(&space, 1, aNotify);
  1011   } else {
  1012     mDisplayContent->SetText(mDisplayedOptionText, aNotify);
  1016 int32_t
  1017 nsComboboxControlFrame::GetIndexOfDisplayArea()
  1019   return mDisplayedIndex;
  1022 //----------------------------------------------------------------------
  1023 // nsISelectControlFrame
  1024 //----------------------------------------------------------------------
  1025 NS_IMETHODIMP
  1026 nsComboboxControlFrame::DoneAddingChildren(bool aIsDone)
  1028   nsISelectControlFrame* listFrame = do_QueryFrame(mDropdownFrame);
  1029   if (!listFrame)
  1030     return NS_ERROR_FAILURE;
  1032   return listFrame->DoneAddingChildren(aIsDone);
  1035 NS_IMETHODIMP
  1036 nsComboboxControlFrame::AddOption(int32_t aIndex)
  1038   if (aIndex <= mDisplayedIndex) {
  1039     ++mDisplayedIndex;
  1042   nsListControlFrame* lcf = static_cast<nsListControlFrame*>(mDropdownFrame);
  1043   return lcf->AddOption(aIndex);
  1047 NS_IMETHODIMP
  1048 nsComboboxControlFrame::RemoveOption(int32_t aIndex)
  1050   nsWeakFrame weakThis(this);
  1051   if (mListControlFrame->GetNumberOfOptions() > 0) {
  1052     if (aIndex < mDisplayedIndex) {
  1053       --mDisplayedIndex;
  1054     } else if (aIndex == mDisplayedIndex) {
  1055       mDisplayedIndex = 0; // IE6 compat
  1056       RedisplayText(mDisplayedIndex);
  1059   else {
  1060     // If we removed the last option, we need to blank things out
  1061     RedisplayText(-1);
  1064   if (!weakThis.IsAlive())
  1065     return NS_OK;
  1067   nsListControlFrame* lcf = static_cast<nsListControlFrame*>(mDropdownFrame);
  1068   return lcf->RemoveOption(aIndex);
  1071 NS_IMETHODIMP
  1072 nsComboboxControlFrame::OnSetSelectedIndex(int32_t aOldIndex, int32_t aNewIndex)
  1074   nsAutoScriptBlocker scriptBlocker;
  1075   RedisplayText(aNewIndex);
  1076   NS_ASSERTION(mDropdownFrame, "No dropdown frame!");
  1078   nsISelectControlFrame* listFrame = do_QueryFrame(mDropdownFrame);
  1079   NS_ASSERTION(listFrame, "No list frame!");
  1081   return listFrame->OnSetSelectedIndex(aOldIndex, aNewIndex);
  1084 // End nsISelectControlFrame
  1085 //----------------------------------------------------------------------
  1087 nsresult
  1088 nsComboboxControlFrame::HandleEvent(nsPresContext* aPresContext,
  1089                                     WidgetGUIEvent* aEvent,
  1090                                     nsEventStatus* aEventStatus)
  1092   NS_ENSURE_ARG_POINTER(aEventStatus);
  1094   if (nsEventStatus_eConsumeNoDefault == *aEventStatus) {
  1095     return NS_OK;
  1098   EventStates eventStates = mContent->AsElement()->State();
  1099   if (eventStates.HasState(NS_EVENT_STATE_DISABLED)) {
  1100     return NS_OK;
  1103   // If we have style that affects how we are selected, feed event down to
  1104   // nsFrame::HandleEvent so that selection takes place when appropriate.
  1105   const nsStyleUserInterface* uiStyle = StyleUserInterface();
  1106   if (uiStyle->mUserInput == NS_STYLE_USER_INPUT_NONE || uiStyle->mUserInput == NS_STYLE_USER_INPUT_DISABLED)
  1107     return nsBlockFrame::HandleEvent(aPresContext, aEvent, aEventStatus);
  1109   return NS_OK;
  1113 nsresult
  1114 nsComboboxControlFrame::SetFormProperty(nsIAtom* aName, const nsAString& aValue)
  1116   nsIFormControlFrame* fcFrame = do_QueryFrame(mDropdownFrame);
  1117   if (!fcFrame) {
  1118     return NS_NOINTERFACE;
  1121   return fcFrame->SetFormProperty(aName, aValue);
  1124 nsIFrame*
  1125 nsComboboxControlFrame::GetContentInsertionFrame() {
  1126   return mInRedisplayText ? mDisplayFrame : mDropdownFrame->GetContentInsertionFrame();
  1129 nsresult
  1130 nsComboboxControlFrame::CreateAnonymousContent(nsTArray<ContentInfo>& aElements)
  1132   // The frames used to display the combo box and the button used to popup the dropdown list
  1133   // are created through anonymous content. The dropdown list is not created through anonymous
  1134   // content because its frame is initialized specifically for the drop-down case and it is placed
  1135   // a special list referenced through NS_COMBO_FRAME_POPUP_LIST_INDEX to keep separate from the
  1136   // layout of the display and button.
  1137   //
  1138   // Note: The value attribute of the display content is set when an item is selected in the dropdown list.
  1139   // If the content specified below does not honor the value attribute than nothing will be displayed.
  1141   // For now the content that is created corresponds to two input buttons. It would be better to create the
  1142   // tag as something other than input, but then there isn't any way to create a button frame since it
  1143   // isn't possible to set the display type in CSS2 to create a button frame.
  1145     // create content used for display
  1146   //nsIAtom* tag = NS_NewAtom("mozcombodisplay");
  1148   // Add a child text content node for the label
  1150   nsNodeInfoManager *nimgr = mContent->NodeInfo()->NodeInfoManager();
  1152   mDisplayContent = new nsTextNode(nimgr);
  1154   // set the value of the text node
  1155   mDisplayedIndex = mListControlFrame->GetSelectedIndex();
  1156   if (mDisplayedIndex != -1) {
  1157     mListControlFrame->GetOptionText(mDisplayedIndex, mDisplayedOptionText);
  1159   ActuallyDisplayText(false);
  1161   if (!aElements.AppendElement(mDisplayContent))
  1162     return NS_ERROR_OUT_OF_MEMORY;
  1164   mButtonContent = mContent->OwnerDoc()->CreateHTMLElement(nsGkAtoms::button);
  1165   if (!mButtonContent)
  1166     return NS_ERROR_OUT_OF_MEMORY;
  1168   // make someone to listen to the button. If its pressed by someone like Accessibility
  1169   // then open or close the combo box.
  1170   mButtonListener = new nsComboButtonListener(this);
  1171   mButtonContent->AddEventListener(NS_LITERAL_STRING("click"), mButtonListener,
  1172                                    false, false);
  1174   mButtonContent->SetAttr(kNameSpaceID_None, nsGkAtoms::type,
  1175                           NS_LITERAL_STRING("button"), false);
  1176   // Set tabindex="-1" so that the button is not tabbable
  1177   mButtonContent->SetAttr(kNameSpaceID_None, nsGkAtoms::tabindex,
  1178                           NS_LITERAL_STRING("-1"), false);
  1180   if (!aElements.AppendElement(mButtonContent))
  1181     return NS_ERROR_OUT_OF_MEMORY;
  1183   return NS_OK;
  1186 void
  1187 nsComboboxControlFrame::AppendAnonymousContentTo(nsBaseContentList& aElements,
  1188                                                  uint32_t aFilter)
  1190   aElements.MaybeAppendElement(mDisplayContent);
  1191   aElements.MaybeAppendElement(mButtonContent);
  1194 // XXXbz this is a for-now hack.  Now that display:inline-block works,
  1195 // need to revisit this.
  1196 class nsComboboxDisplayFrame : public nsBlockFrame {
  1197 public:
  1198   NS_DECL_FRAMEARENA_HELPERS
  1200   nsComboboxDisplayFrame (nsStyleContext* aContext,
  1201                           nsComboboxControlFrame* aComboBox)
  1202     : nsBlockFrame(aContext),
  1203       mComboBox(aComboBox)
  1204   {}
  1206   // Need this so that line layout knows that this block's width
  1207   // depends on the available width.
  1208   virtual nsIAtom* GetType() const MOZ_OVERRIDE;
  1210   virtual bool IsFrameOfType(uint32_t aFlags) const MOZ_OVERRIDE
  1212     return nsBlockFrame::IsFrameOfType(aFlags &
  1213       ~(nsIFrame::eReplacedContainsBlock));
  1216   virtual nsresult Reflow(nsPresContext*           aPresContext,
  1217                           nsHTMLReflowMetrics&     aDesiredSize,
  1218                           const nsHTMLReflowState& aReflowState,
  1219                           nsReflowStatus&          aStatus) MOZ_OVERRIDE;
  1221   virtual void BuildDisplayList(nsDisplayListBuilder*   aBuilder,
  1222                                 const nsRect&           aDirtyRect,
  1223                                 const nsDisplayListSet& aLists) MOZ_OVERRIDE;
  1225 protected:
  1226   nsComboboxControlFrame* mComboBox;
  1227 };
  1229 NS_IMPL_FRAMEARENA_HELPERS(nsComboboxDisplayFrame)
  1231 nsIAtom*
  1232 nsComboboxDisplayFrame::GetType() const
  1234   return nsGkAtoms::comboboxDisplayFrame;
  1237 nsresult
  1238 nsComboboxDisplayFrame::Reflow(nsPresContext*           aPresContext,
  1239                                nsHTMLReflowMetrics&     aDesiredSize,
  1240                                const nsHTMLReflowState& aReflowState,
  1241                                nsReflowStatus&          aStatus)
  1243   nsHTMLReflowState state(aReflowState);
  1244   if (state.ComputedHeight() == NS_INTRINSICSIZE) {
  1245     // Note that the only way we can have a computed height here is if the
  1246     // combobox had a specified height.  If it didn't, size based on what our
  1247     // rows look like, for lack of anything better.
  1248     state.SetComputedHeight(mComboBox->mListControlFrame->GetHeightOfARow());
  1250   nscoord computedWidth = mComboBox->mDisplayWidth -
  1251     state.ComputedPhysicalBorderPadding().LeftRight();
  1252   if (computedWidth < 0) {
  1253     computedWidth = 0;
  1255   state.SetComputedWidth(computedWidth);
  1257   return nsBlockFrame::Reflow(aPresContext, aDesiredSize, state, aStatus);
  1260 void
  1261 nsComboboxDisplayFrame::BuildDisplayList(nsDisplayListBuilder*   aBuilder,
  1262                                          const nsRect&           aDirtyRect,
  1263                                          const nsDisplayListSet& aLists)
  1265   nsDisplayListCollection set;
  1266   nsBlockFrame::BuildDisplayList(aBuilder, aDirtyRect, set);
  1268   // remove background items if parent frame is themed
  1269   if (mComboBox->IsThemed()) {
  1270     set.BorderBackground()->DeleteAll();
  1273   set.MoveTo(aLists);
  1276 nsIFrame*
  1277 nsComboboxControlFrame::CreateFrameFor(nsIContent*      aContent)
  1279   NS_PRECONDITION(nullptr != aContent, "null ptr");
  1281   NS_ASSERTION(mDisplayContent, "mDisplayContent can't be null!");
  1283   if (mDisplayContent != aContent) {
  1284     // We only handle the frames for mDisplayContent here
  1285     return nullptr;
  1288   // Get PresShell
  1289   nsIPresShell *shell = PresContext()->PresShell();
  1290   nsStyleSet *styleSet = shell->StyleSet();
  1292   // create the style contexts for the anonymous block frame and text frame
  1293   nsRefPtr<nsStyleContext> styleContext;
  1294   styleContext = styleSet->
  1295     ResolveAnonymousBoxStyle(nsCSSAnonBoxes::mozDisplayComboboxControlFrame,
  1296                              mStyleContext);
  1298   nsRefPtr<nsStyleContext> textStyleContext;
  1299   textStyleContext = styleSet->ResolveStyleForNonElement(mStyleContext);
  1301   // Start by creating our anonymous block frame
  1302   mDisplayFrame = new (shell) nsComboboxDisplayFrame(styleContext, this);
  1303   mDisplayFrame->Init(mContent, this, nullptr);
  1305   // Create a text frame and put it inside the block frame
  1306   nsIFrame* textFrame = NS_NewTextFrame(shell, textStyleContext);
  1308   // initialize the text frame
  1309   textFrame->Init(aContent, mDisplayFrame, nullptr);
  1310   mDisplayContent->SetPrimaryFrame(textFrame);
  1312   nsFrameList textList(textFrame, textFrame);
  1313   mDisplayFrame->SetInitialChildList(kPrincipalList, textList);
  1314   return mDisplayFrame;
  1317 void
  1318 nsComboboxControlFrame::DestroyFrom(nsIFrame* aDestructRoot)
  1320   // Revoke any pending RedisplayTextEvent
  1321   mRedisplayTextEvent.Revoke();
  1323   nsFormControlFrame::RegUnRegAccessKey(static_cast<nsIFrame*>(this), false);
  1325   if (mDroppedDown) {
  1326     MOZ_ASSERT(mDropdownFrame, "mDroppedDown without frame");
  1327     nsView* view = mDropdownFrame->GetView();
  1328     MOZ_ASSERT(view);
  1329     nsIWidget* widget = view->GetWidget();
  1330     if (widget) {
  1331       widget->CaptureRollupEvents(this, false);
  1335   // Cleanup frames in popup child list
  1336   mPopupFrames.DestroyFramesFrom(aDestructRoot);
  1337   nsContentUtils::DestroyAnonymousContent(&mDisplayContent);
  1338   nsContentUtils::DestroyAnonymousContent(&mButtonContent);
  1339   nsBlockFrame::DestroyFrom(aDestructRoot);
  1342 const nsFrameList&
  1343 nsComboboxControlFrame::GetChildList(ChildListID aListID) const
  1345   if (kSelectPopupList == aListID) {
  1346     return mPopupFrames;
  1348   return nsBlockFrame::GetChildList(aListID);
  1351 void
  1352 nsComboboxControlFrame::GetChildLists(nsTArray<ChildList>* aLists) const
  1354   nsBlockFrame::GetChildLists(aLists);
  1355   mPopupFrames.AppendIfNonempty(aLists, kSelectPopupList);
  1358 nsresult
  1359 nsComboboxControlFrame::SetInitialChildList(ChildListID     aListID,
  1360                                             nsFrameList&    aChildList)
  1362   nsresult rv = NS_OK;
  1363   if (kSelectPopupList == aListID) {
  1364     mPopupFrames.SetFrames(aChildList);
  1365   } else {
  1366     for (nsFrameList::Enumerator e(aChildList); !e.AtEnd(); e.Next()) {
  1367       nsCOMPtr<nsIFormControl> formControl =
  1368         do_QueryInterface(e.get()->GetContent());
  1369       if (formControl && formControl->GetType() == NS_FORM_BUTTON_BUTTON) {
  1370         mButtonFrame = e.get();
  1371         break;
  1374     NS_ASSERTION(mButtonFrame, "missing button frame in initial child list");
  1375     rv = nsBlockFrame::SetInitialChildList(aListID, aChildList);
  1377   return rv;
  1380 //----------------------------------------------------------------------
  1381   //nsIRollupListener
  1382 //----------------------------------------------------------------------
  1383 bool
  1384 nsComboboxControlFrame::Rollup(uint32_t aCount, const nsIntPoint* pos, nsIContent** aLastRolledUp)
  1386   if (!mDroppedDown)
  1387     return false;
  1389   nsWeakFrame weakFrame(this);
  1390   mListControlFrame->AboutToRollup(); // might destroy us
  1391   if (!weakFrame.IsAlive())
  1392     return true;
  1393   ShowDropDown(false); // might destroy us
  1394   if (weakFrame.IsAlive()) {
  1395     mListControlFrame->CaptureMouseEvents(false);
  1398   return true;
  1401 nsIWidget*
  1402 nsComboboxControlFrame::GetRollupWidget()
  1404   nsView* view = mDropdownFrame->GetView();
  1405   MOZ_ASSERT(view);
  1406   return view->GetWidget();
  1409 void
  1410 nsComboboxControlFrame::RollupFromList()
  1412   if (ShowList(false))
  1413     mListControlFrame->CaptureMouseEvents(false);
  1416 int32_t
  1417 nsComboboxControlFrame::UpdateRecentIndex(int32_t aIndex)
  1419   int32_t index = mRecentSelectedIndex;
  1420   if (mRecentSelectedIndex == NS_SKIP_NOTIFY_INDEX || aIndex == NS_SKIP_NOTIFY_INDEX)
  1421     mRecentSelectedIndex = aIndex;
  1422   return index;
  1425 class nsDisplayComboboxFocus : public nsDisplayItem {
  1426 public:
  1427   nsDisplayComboboxFocus(nsDisplayListBuilder* aBuilder,
  1428                          nsComboboxControlFrame* aFrame)
  1429     : nsDisplayItem(aBuilder, aFrame) {
  1430     MOZ_COUNT_CTOR(nsDisplayComboboxFocus);
  1432 #ifdef NS_BUILD_REFCNT_LOGGING
  1433   virtual ~nsDisplayComboboxFocus() {
  1434     MOZ_COUNT_DTOR(nsDisplayComboboxFocus);
  1436 #endif
  1438   virtual void Paint(nsDisplayListBuilder* aBuilder,
  1439                      nsRenderingContext* aCtx) MOZ_OVERRIDE;
  1440   NS_DISPLAY_DECL_NAME("ComboboxFocus", TYPE_COMBOBOX_FOCUS)
  1441 };
  1443 void nsDisplayComboboxFocus::Paint(nsDisplayListBuilder* aBuilder,
  1444                                    nsRenderingContext* aCtx)
  1446   static_cast<nsComboboxControlFrame*>(mFrame)
  1447     ->PaintFocus(*aCtx, ToReferenceFrame());
  1450 void
  1451 nsComboboxControlFrame::BuildDisplayList(nsDisplayListBuilder*   aBuilder,
  1452                                          const nsRect&           aDirtyRect,
  1453                                          const nsDisplayListSet& aLists)
  1455 #ifdef NOISY
  1456   printf("%p paint at (%d, %d, %d, %d)\n", this,
  1457     aDirtyRect.x, aDirtyRect.y, aDirtyRect.width, aDirtyRect.height);
  1458 #endif
  1460   if (aBuilder->IsForEventDelivery()) {
  1461     // Don't allow children to receive events.
  1462     // REVIEW: following old GetFrameForPoint
  1463     DisplayBorderBackgroundOutline(aBuilder, aLists);
  1464   } else {
  1465     // REVIEW: Our in-flow child frames are inline-level so they will paint in our
  1466     // content list, so we don't need to mess with layers.
  1467     nsBlockFrame::BuildDisplayList(aBuilder, aDirtyRect, aLists);
  1470   // draw a focus indicator only when focus rings should be drawn
  1471   nsIDocument* doc = mContent->GetCurrentDoc();
  1472   if (doc) {
  1473     nsPIDOMWindow* window = doc->GetWindow();
  1474     if (window && window->ShouldShowFocusRing()) {
  1475       nsPresContext *presContext = PresContext();
  1476       const nsStyleDisplay *disp = StyleDisplay();
  1477       if ((!IsThemed(disp) ||
  1478            !presContext->GetTheme()->ThemeDrawsFocusForWidget(disp->mAppearance)) &&
  1479           mDisplayFrame && IsVisibleForPainting(aBuilder)) {
  1480         aLists.Content()->AppendNewToTop(
  1481           new (aBuilder) nsDisplayComboboxFocus(aBuilder, this));
  1486   DisplaySelectionOverlay(aBuilder, aLists.Content());
  1489 void nsComboboxControlFrame::PaintFocus(nsRenderingContext& aRenderingContext,
  1490                                         nsPoint aPt)
  1492   /* Do we need to do anything? */
  1493   EventStates eventStates = mContent->AsElement()->State();
  1494   if (eventStates.HasState(NS_EVENT_STATE_DISABLED) || sFocused != this)
  1495     return;
  1497   aRenderingContext.PushState();
  1498   nsRect clipRect = mDisplayFrame->GetRect() + aPt;
  1499   aRenderingContext.IntersectClip(clipRect);
  1501   // REVIEW: Why does the old code paint mDisplayFrame again? We've
  1502   // already painted it in the children above. So clipping it here won't do
  1503   // us much good.
  1505   /////////////////////
  1506   // draw focus
  1508   aRenderingContext.SetLineStyle(nsLineStyle_kDotted);
  1509   aRenderingContext.SetColor(StyleColor()->mColor);
  1511   //aRenderingContext.DrawRect(clipRect);
  1513   nscoord onePixel = nsPresContext::CSSPixelsToAppUnits(1);
  1514   clipRect.width -= onePixel;
  1515   clipRect.height -= onePixel;
  1516   aRenderingContext.DrawLine(clipRect.TopLeft(), clipRect.TopRight());
  1517   aRenderingContext.DrawLine(clipRect.TopRight(), clipRect.BottomRight());
  1518   aRenderingContext.DrawLine(clipRect.BottomRight(), clipRect.BottomLeft());
  1519   aRenderingContext.DrawLine(clipRect.BottomLeft(), clipRect.TopLeft());
  1521   aRenderingContext.PopState();
  1524 //---------------------------------------------------------
  1525 // gets the content (an option) by index and then set it as
  1526 // being selected or not selected
  1527 //---------------------------------------------------------
  1528 NS_IMETHODIMP
  1529 nsComboboxControlFrame::OnOptionSelected(int32_t aIndex, bool aSelected)
  1531   if (mDroppedDown) {
  1532     nsISelectControlFrame *selectFrame = do_QueryFrame(mListControlFrame);
  1533     if (selectFrame) {
  1534       selectFrame->OnOptionSelected(aIndex, aSelected);
  1536   } else {
  1537     if (aSelected) {
  1538       nsAutoScriptBlocker blocker;
  1539       RedisplayText(aIndex);
  1540     } else {
  1541       nsWeakFrame weakFrame(this);
  1542       RedisplaySelectedText();
  1543       if (weakFrame.IsAlive()) {
  1544         FireValueChangeEvent(); // Fire after old option is unselected
  1549   return NS_OK;
  1552 void nsComboboxControlFrame::FireValueChangeEvent()
  1554   // Fire ValueChange event to indicate data value of combo box has changed
  1555   nsContentUtils::AddScriptRunner(
  1556     new AsyncEventDispatcher(mContent, NS_LITERAL_STRING("ValueChange"), true,
  1557                              false));
  1560 void
  1561 nsComboboxControlFrame::OnContentReset()
  1563   if (mListControlFrame) {
  1564     mListControlFrame->OnContentReset();
  1569 //--------------------------------------------------------
  1570 // nsIStatefulFrame
  1571 //--------------------------------------------------------
  1572 NS_IMETHODIMP
  1573 nsComboboxControlFrame::SaveState(nsPresState** aState)
  1575   if (!mListControlFrame)
  1576     return NS_ERROR_FAILURE;
  1578   nsIStatefulFrame* stateful = do_QueryFrame(mListControlFrame);
  1579   return stateful->SaveState(aState);
  1582 NS_IMETHODIMP
  1583 nsComboboxControlFrame::RestoreState(nsPresState* aState)
  1585   if (!mListControlFrame)
  1586     return NS_ERROR_FAILURE;
  1588   nsIStatefulFrame* stateful = do_QueryFrame(mListControlFrame);
  1589   NS_ASSERTION(stateful, "Must implement nsIStatefulFrame");
  1590   return stateful->RestoreState(aState);
  1594 //
  1595 // Camino uses a native widget for the combobox
  1596 // popup, which affects drawing and event
  1597 // handling here and in nsListControlFrame.
  1598 //
  1599 // Also, Fennec use a custom combobox built-in widget
  1600 //
  1602 /* static */
  1603 bool
  1604 nsComboboxControlFrame::ToolkitHasNativePopup()
  1606 #ifdef MOZ_USE_NATIVE_POPUP_WINDOWS
  1607   return true;
  1608 #else
  1609   return false;
  1610 #endif /* MOZ_USE_NATIVE_POPUP_WINDOWS */

mercurial