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 "nsReadableUtils.h"
7 #include "nsComboboxControlFrame.h"
8 #include "nsIDOMEventTarget.h"
9 #include "nsFrameManager.h"
10 #include "nsFormControlFrame.h"
11 #include "nsGfxButtonControlFrame.h"
12 #include "nsGkAtoms.h"
13 #include "nsCSSAnonBoxes.h"
14 #include "nsHTMLParts.h"
15 #include "nsIFormControl.h"
16 #include "nsINameSpaceManager.h"
17 #include "nsIDOMElement.h"
18 #include "nsIListControlFrame.h"
19 #include "nsIDOMHTMLCollection.h"
20 #include "nsIDOMHTMLSelectElement.h"
21 #include "nsIDOMHTMLOptionElement.h"
22 #include "nsPIDOMWindow.h"
23 #include "nsIPresShell.h"
24 #include "nsContentList.h"
26 #include "nsIViewManager.h"
27 #include "nsEventDispatcher.h"
28 #include "nsEventListenerManager.h"
29 #include "nsIDOMNode.h"
30 #include "nsISelectControlFrame.h"
32 #include "nsISupportsPrimitives.h"
33 #include "nsIComponentManager.h"
34 #include "nsContentUtils.h"
35 #include "nsTextFragment.h"
36 #include "nsCSSFrameConstructor.h"
37 #include "nsIDocument.h"
38 #include "nsINodeInfo.h"
39 #include "nsIScrollableFrame.h"
40 #include "nsListControlFrame.h"
41 #include "nsContentCID.h"
43 #include "nsAccessibilityService.h"
45 #include "nsIServiceManager.h"
46 #include "nsGUIEvent.h"
47 #include "nsAutoPtr.h"
48 #include "nsStyleSet.h"
49 #include "nsNodeInfoManager.h"
50 #include "nsContentCreatorFunctions.h"
51 #include "nsLayoutUtils.h"
52 #include "nsDisplayList.h"
54 #include "nsThemeConstants.h"
55 #include "nsAsyncDOMEvent.h"
56 #include "nsRenderingContext.h"
57 #include "mozilla/Preferences.h"
59 using namespace mozilla
;
62 nsComboboxControlFrame::RedisplayTextEvent::Run()
65 mControlFrame
->HandleRedisplayTextEvent();
71 #define FIX_FOR_BUG_53259
73 // Drop down list event management.
74 // The combo box uses the following strategy for managing the drop-down list.
75 // If the combo box or it's arrow button is clicked on the drop-down list is displayed
76 // If mouse exit's the combo box with the drop-down list displayed the drop-down list
77 // is asked to capture events
78 // The drop-down list will capture all events including mouse down and up and will always
79 // return with ListWasSelected method call regardless of whether an item in the list was
81 // The ListWasSelected code will turn off mouse-capture for the drop-down list.
82 // The drop-down list does not explicitly set capture when it is in the drop-down mode.
85 //XXX: This is temporary. It simulates pseudo states by using a attribute selector on
87 const int32_t kSizeNotSet
= -1;
90 * Helper class that listens to the combo boxes button. If the button is pressed the
91 * combo box is toggled to open or close. this is used by Accessibility which presses
92 * that button Programmatically.
94 class nsComboButtonListener
: public nsIDOMEventListener
99 NS_IMETHOD
HandleEvent(nsIDOMEvent
*)
101 mComboBox
->ShowDropDown(!mComboBox
->IsDroppedDown());
105 nsComboButtonListener(nsComboboxControlFrame
* aCombobox
)
107 mComboBox
= aCombobox
;
110 virtual ~nsComboButtonListener() {}
112 nsComboboxControlFrame
* mComboBox
;
115 NS_IMPL_ISUPPORTS1(nsComboButtonListener
,
118 // static class data member for Bug 32920
119 nsComboboxControlFrame
* nsComboboxControlFrame::sFocused
= nullptr;
122 NS_NewComboboxControlFrame(nsIPresShell
* aPresShell
, nsStyleContext
* aContext
, uint32_t aStateFlags
)
124 nsComboboxControlFrame
* it
= new (aPresShell
) nsComboboxControlFrame(aContext
);
127 // set the state flags (if any are provided)
128 it
->AddStateBits(aStateFlags
);
134 NS_IMPL_FRAMEARENA_HELPERS(nsComboboxControlFrame
)
138 class DestroyWidgetRunnable
: public nsRunnable
{
142 explicit DestroyWidgetRunnable(nsIContent
* aCombobox
) :
143 mCombobox(aCombobox
),
149 nsIWidget
* GetWidget(nsIView
** aOutView
= nullptr) const;
152 nsCOMPtr
<nsIContent
> mCombobox
;
156 NS_IMETHODIMP
DestroyWidgetRunnable::Run()
158 nsIView
* view
= nullptr;
159 nsIWidget
* currentWidget
= GetWidget(&view
);
160 // Make sure that we are destroying the same widget as what was requested
161 // when the event was fired.
162 if (view
&& mWidget
&& mWidget
== currentWidget
) {
163 view
->DestroyWidget();
168 nsIWidget
* DestroyWidgetRunnable::GetWidget(nsIView
** aOutView
) const
170 nsIFrame
* primaryFrame
= mCombobox
->GetPrimaryFrame();
171 nsIComboboxControlFrame
* comboboxFrame
= do_QueryFrame(primaryFrame
);
173 nsIFrame
* dropdown
= comboboxFrame
->GetDropDown();
175 nsIView
* view
= dropdown
->GetView();
176 NS_ASSERTION(view
, "nsComboboxControlFrame view is null");
181 return view
->GetWidget();
190 //-----------------------------------------------------------
191 // Reflow Debugging Macros
192 // These let us "see" how many reflow counts are happening
193 //-----------------------------------------------------------
194 #ifdef DO_REFLOW_COUNTER
196 #define MAX_REFLOW_CNT 1024
197 static int32_t gTotalReqs
= 0;;
198 static int32_t gTotalReflows
= 0;;
199 static int32_t gReflowControlCntRQ
[MAX_REFLOW_CNT
];
200 static int32_t gReflowControlCnt
[MAX_REFLOW_CNT
];
201 static int32_t gReflowInx
= -1;
203 #define REFLOW_COUNTER() \
204 if (mReflowId > -1) \
205 gReflowControlCnt[mReflowId]++;
207 #define REFLOW_COUNTER_REQUEST() \
208 if (mReflowId > -1) \
209 gReflowControlCntRQ[mReflowId]++;
211 #define REFLOW_COUNTER_DUMP(__desc) \
212 if (mReflowId > -1) {\
213 gTotalReqs += gReflowControlCntRQ[mReflowId];\
214 gTotalReflows += gReflowControlCnt[mReflowId];\
215 printf("** Id:%5d %s RF: %d RQ: %d %d/%d %5.2f\n", \
216 mReflowId, (__desc), \
217 gReflowControlCnt[mReflowId], \
218 gReflowControlCntRQ[mReflowId],\
219 gTotalReflows, gTotalReqs, float(gTotalReflows)/float(gTotalReqs)*100.0f);\
222 #define REFLOW_COUNTER_INIT() \
223 if (gReflowInx < MAX_REFLOW_CNT) { \
225 mReflowId = gReflowInx; \
226 gReflowControlCnt[mReflowId] = 0; \
227 gReflowControlCntRQ[mReflowId] = 0; \
233 #define REFLOW_DEBUG_MSG(_msg1) printf((_msg1))
234 #define REFLOW_DEBUG_MSG2(_msg1, _msg2) printf((_msg1), (_msg2))
235 #define REFLOW_DEBUG_MSG3(_msg1, _msg2, _msg3) printf((_msg1), (_msg2), (_msg3))
236 #define REFLOW_DEBUG_MSG4(_msg1, _msg2, _msg3, _msg4) printf((_msg1), (_msg2), (_msg3), (_msg4))
238 #else //-------------
240 #define REFLOW_COUNTER_REQUEST()
241 #define REFLOW_COUNTER()
242 #define REFLOW_COUNTER_DUMP(__desc)
243 #define REFLOW_COUNTER_INIT()
245 #define REFLOW_DEBUG_MSG(_msg)
246 #define REFLOW_DEBUG_MSG2(_msg1, _msg2)
247 #define REFLOW_DEBUG_MSG3(_msg1, _msg2, _msg3)
248 #define REFLOW_DEBUG_MSG4(_msg1, _msg2, _msg3, _msg4)
253 //------------------------------------------
254 // This is for being VERY noisy
255 //------------------------------------------
257 #define REFLOW_NOISY_MSG(_msg1) printf((_msg1))
258 #define REFLOW_NOISY_MSG2(_msg1, _msg2) printf((_msg1), (_msg2))
259 #define REFLOW_NOISY_MSG3(_msg1, _msg2, _msg3) printf((_msg1), (_msg2), (_msg3))
260 #define REFLOW_NOISY_MSG4(_msg1, _msg2, _msg3, _msg4) printf((_msg1), (_msg2), (_msg3), (_msg4))
262 #define REFLOW_NOISY_MSG(_msg)
263 #define REFLOW_NOISY_MSG2(_msg1, _msg2)
264 #define REFLOW_NOISY_MSG3(_msg1, _msg2, _msg3)
265 #define REFLOW_NOISY_MSG4(_msg1, _msg2, _msg3, _msg4)
268 //------------------------------------------
269 // Displays value in pixels or twips
270 //------------------------------------------
272 #define PX(__v) __v / 15
277 //------------------------------------------------------
278 //-- Done with macros
279 //------------------------------------------------------
281 nsComboboxControlFrame::nsComboboxControlFrame(nsStyleContext
* aContext
)
282 : nsBlockFrame(aContext
)
283 , mDisplayFrame(nullptr)
284 , mButtonFrame(nullptr)
285 , mDropdownFrame(nullptr)
286 , mListControlFrame(nullptr)
288 , mRecentSelectedIndex(NS_SKIP_NOTIFY_INDEX
)
289 , mDisplayedIndex(-1)
290 , mDroppedDown(false)
291 , mInRedisplayText(false)
292 , mDelayedShowDropDown(false)
294 REFLOW_COUNTER_INIT()
297 //--------------------------------------------------------------
298 nsComboboxControlFrame::~nsComboboxControlFrame()
300 REFLOW_COUNTER_DUMP("nsCCF");
303 //--------------------------------------------------------------
305 NS_QUERYFRAME_HEAD(nsComboboxControlFrame
)
306 NS_QUERYFRAME_ENTRY(nsIComboboxControlFrame
)
307 NS_QUERYFRAME_ENTRY(nsIFormControlFrame
)
308 NS_QUERYFRAME_ENTRY(nsIAnonymousContentCreator
)
309 NS_QUERYFRAME_ENTRY(nsISelectControlFrame
)
310 NS_QUERYFRAME_ENTRY(nsIStatefulFrame
)
311 NS_QUERYFRAME_TAIL_INHERITING(nsBlockFrame
)
314 already_AddRefed
<Accessible
>
315 nsComboboxControlFrame::CreateAccessible()
317 nsAccessibilityService
* accService
= nsIPresShell::AccService();
319 return accService
->CreateHTMLComboboxAccessible(mContent
,
320 PresContext()->PresShell());
328 nsComboboxControlFrame::SetFocus(bool aOn
, bool aRepaint
)
330 nsWeakFrame
weakFrame(this);
332 nsListControlFrame::ComboboxFocusSet();
334 if (mDelayedShowDropDown
) {
335 ShowDropDown(true); // might destroy us
336 if (!weakFrame
.IsAlive()) {
342 mDelayedShowDropDown
= false;
344 mListControlFrame
->ComboboxFinish(mDisplayedIndex
); // might destroy us
345 if (!weakFrame
.IsAlive()) {
349 // May delete |this|.
350 mListControlFrame
->FireOnChange();
353 if (!weakFrame
.IsAlive()) {
357 // This is needed on a temporary basis. It causes the focus
358 // rect to be drawn. This is much faster than ReResolvingStyle
360 Invalidate(nsRect(0,0,mRect
.width
,mRect
.height
));
364 nsComboboxControlFrame::ShowPopup(bool aShowPopup
)
366 nsIView
* view
= mDropdownFrame
->GetView();
367 nsIViewManager
* viewManager
= view
->GetViewManager();
370 nsRect rect
= mDropdownFrame
->GetRect();
372 viewManager
->ResizeView(view
, rect
);
373 viewManager
->SetViewVisibility(view
, nsViewVisibility_kShow
);
375 viewManager
->SetViewVisibility(view
, nsViewVisibility_kHide
);
376 nsRect
emptyRect(0, 0, 0, 0);
377 viewManager
->ResizeView(view
, emptyRect
);
380 // fire a popup dom event
381 nsEventStatus status
= nsEventStatus_eIgnore
;
382 nsMouseEvent
event(true, aShowPopup
?
383 NS_XUL_POPUP_SHOWING
: NS_XUL_POPUP_HIDING
, nullptr,
384 nsMouseEvent::eReal
);
386 nsCOMPtr
<nsIPresShell
> shell
= PresContext()->GetPresShell();
388 shell
->HandleDOMEventWithTarget(mContent
, &event
, &status
);
392 nsComboboxControlFrame::ShowList(bool aShowList
)
394 nsCOMPtr
<nsIPresShell
> shell
= PresContext()->GetPresShell();
396 nsWeakFrame
weakFrame(this);
399 nsIView
* view
= mDropdownFrame
->GetView();
400 NS_ASSERTION(!view
->HasWidget(),
401 "We shoudldn't have a widget before we need to display the popup");
403 // Create the widget for the drop-down list
404 view
->GetViewManager()->SetViewFloating(view
, true);
406 nsWidgetInitData widgetData
;
407 widgetData
.mWindowType
= eWindowType_popup
;
408 widgetData
.mBorderStyle
= eBorderStyle_default
;
409 view
->CreateWidgetForPopup(&widgetData
);
412 ShowPopup(aShowList
); // might destroy us
413 if (!weakFrame
.IsAlive()) {
417 mDroppedDown
= aShowList
;
419 // The listcontrol frame will call back to the nsComboboxControlFrame's
420 // ListWasSelected which will stop the capture.
421 mListControlFrame
->AboutToDropDown();
422 mListControlFrame
->CaptureMouseEvents(true);
425 // XXXbz so why do we need to flush here, exactly?
426 shell
->GetDocument()->FlushPendingNotifications(Flush_Layout
);
427 if (!weakFrame
.IsAlive()) {
431 nsIFrame
* listFrame
= do_QueryFrame(mListControlFrame
);
433 nsIView
* view
= listFrame
->GetView();
434 NS_ASSERTION(view
, "nsComboboxControlFrame view is null");
436 nsIWidget
* widget
= view
->GetWidget();
438 widget
->CaptureRollupEvents(this, mDroppedDown
, mDroppedDown
);
441 nsCOMPtr
<nsIRunnable
> widgetDestroyer
=
442 new DestroyWidgetRunnable(GetContent());
443 NS_DispatchToMainThread(widgetDestroyer
);
449 return weakFrame
.IsAlive();
452 class nsResizeDropdownAtFinalPosition
453 : public nsIReflowCallback
, public nsRunnable
456 nsResizeDropdownAtFinalPosition(nsComboboxControlFrame
* aFrame
)
459 MOZ_COUNT_CTOR(nsResizeDropdownAtFinalPosition
);
461 ~nsResizeDropdownAtFinalPosition()
463 MOZ_COUNT_DTOR(nsResizeDropdownAtFinalPosition
);
466 virtual bool ReflowFinished()
473 virtual void ReflowCallbackCanceled()
480 if (mFrame
.IsAlive()) {
481 static_cast<nsComboboxControlFrame
*>(mFrame
.GetFrame())->
482 AbsolutelyPositionDropDown();
491 nsComboboxControlFrame::ReflowDropdown(nsPresContext
* aPresContext
,
492 const nsHTMLReflowState
& aReflowState
)
494 // All we want out of it later on, really, is the height of a row, so we
495 // don't even need to cache mDropdownFrame's ascent or anything. If we don't
496 // need to reflow it, just bail out here.
497 if (!aReflowState
.ShouldReflowAllKids() &&
498 !NS_SUBTREE_DIRTY(mDropdownFrame
)) {
502 // XXXbz this will, for small-height dropdowns, have extra space on the right
503 // edge for the scrollbar we don't show... but that's the best we can do here
505 nsSize
availSize(aReflowState
.availableWidth
, NS_UNCONSTRAINEDSIZE
);
506 nsHTMLReflowState
kidReflowState(aPresContext
, aReflowState
, mDropdownFrame
,
509 // If the dropdown's intrinsic width is narrower than our specified width,
510 // then expand it out. We want our border-box width to end up the same as
511 // the dropdown's so account for both sets of mComputedBorderPadding.
512 nscoord forcedWidth
= aReflowState
.ComputedWidth() +
513 aReflowState
.mComputedBorderPadding
.LeftRight() -
514 kidReflowState
.mComputedBorderPadding
.LeftRight();
515 kidReflowState
.SetComputedWidth(NS_MAX(kidReflowState
.ComputedWidth(),
518 // ensure we start off hidden
519 if (GetStateBits() & NS_FRAME_FIRST_REFLOW
) {
520 nsIView
* view
= mDropdownFrame
->GetView();
521 nsIViewManager
* viewManager
= view
->GetViewManager();
522 viewManager
->SetViewVisibility(view
, nsViewVisibility_kHide
);
523 nsRect
emptyRect(0, 0, 0, 0);
524 viewManager
->ResizeView(view
, emptyRect
);
527 // Allow the child to move/size/change-visibility its view if it's currently
529 int32_t flags
= NS_FRAME_NO_MOVE_FRAME
| NS_FRAME_NO_VISIBILITY
| NS_FRAME_NO_SIZE_VIEW
;
533 nsRect rect
= mDropdownFrame
->GetRect();
534 nsHTMLReflowMetrics desiredSize
;
535 nsReflowStatus ignoredStatus
;
536 nsresult rv
= ReflowChild(mDropdownFrame
, aPresContext
, desiredSize
,
537 kidReflowState
, rect
.x
, rect
.y
, flags
,
540 // Set the child's width and height to it's desired size
541 FinishReflowChild(mDropdownFrame
, aPresContext
, &kidReflowState
,
542 desiredSize
, rect
.x
, rect
.y
, flags
);
547 nsComboboxControlFrame::GetCSSTransformTranslation()
549 nsIFrame
* frame
= this;
550 bool is3DTransform
= false;
554 gfx3DMatrix ctm
= frame
->GetTransformMatrix(nullptr, &parent
);
556 if (ctm
.Is2D(&matrix
)) {
557 transform
= transform
* matrix
;
559 is3DTransform
= true;
565 if (!is3DTransform
&& !transform
.HasNonTranslation()) {
566 nsPresContext
* pc
= PresContext();
567 gfxPoint pixelTranslation
= transform
.GetTranslation();
568 int32_t apd
= pc
->AppUnitsPerDevPixel();
569 translation
.x
= NSFloatPixelsToAppUnits(float(pixelTranslation
.x
), apd
);
570 translation
.y
= NSFloatPixelsToAppUnits(float(pixelTranslation
.y
), apd
);
571 // To get the translation introduced only by transforms we subtract the
572 // regular non-transform translation.
573 nsRootPresContext
* rootPC
= pc
->GetRootPresContext();
575 translation
-= GetOffsetToCrossDoc(rootPC
->PresShell()->GetRootFrame());
577 translation
.x
= translation
.y
= 0;
583 class nsAsyncRollup
: public nsRunnable
586 nsAsyncRollup(nsComboboxControlFrame
* aFrame
) : mFrame(aFrame
) {}
589 if (mFrame
.IsAlive()) {
590 static_cast<nsComboboxControlFrame
*>(mFrame
.GetFrame())
598 class nsAsyncResize
: public nsRunnable
601 nsAsyncResize(nsComboboxControlFrame
* aFrame
) : mFrame(aFrame
) {}
604 if (mFrame
.IsAlive()) {
605 nsComboboxControlFrame
* combo
=
606 static_cast<nsComboboxControlFrame
*>(mFrame
.GetFrame());
607 static_cast<nsListControlFrame
*>(combo
->mDropdownFrame
)->
608 SetSuppressScrollbarUpdate(true);
609 nsCOMPtr
<nsIPresShell
> shell
= mFrame
->PresContext()->PresShell();
610 shell
->FrameNeedsReflow(combo
->mDropdownFrame
, nsIPresShell::eResize
,
612 shell
->FlushPendingNotifications(Flush_Layout
);
613 if (mFrame
.IsAlive()) {
614 combo
= static_cast<nsComboboxControlFrame
*>(mFrame
.GetFrame());
615 static_cast<nsListControlFrame
*>(combo
->mDropdownFrame
)->
616 SetSuppressScrollbarUpdate(false);
617 if (combo
->mDelayedShowDropDown
) {
618 combo
->ShowDropDown(true);
628 nsComboboxControlFrame::GetAvailableDropdownSpace(nscoord
* aAbove
,
630 nsPoint
* aTranslation
)
632 // Note: At first glance, it appears that you could simply get the absolute
633 // bounding box for the dropdown list by first getting its view, then getting
634 // the view's nsIWidget, then asking the nsIWidget for its AbsoluteBounds.
635 // The problem with this approach, is that the dropdown lists y location can
636 // change based on whether the dropdown is placed below or above the display
637 // frame. The approach, taken here is to get the absolute position of the
638 // display frame and use its location to determine if the dropdown will go
641 // Normal frame geometry (eg GetOffsetTo, mRect) doesn't include transforms.
642 // In the special case that our transform is only a 2D translation we
643 // introduce this hack so that the dropdown will show up in the right place.
644 *aTranslation
= GetCSSTransformTranslation();
648 nsRect thisScreenRect
= GetScreenRectInAppUnits();
649 nsRect screen
= nsFormControlFrame::GetUsableScreenRect(PresContext());
650 nscoord dropdownY
= thisScreenRect
.YMost() + aTranslation
->y
;
653 if (!PresContext()->IsChrome()) {
654 nsIFrame
* root
= PresContext()->PresShell()->GetRootFrame();
655 minY
= root
->GetScreenRectInAppUnits().y
;
656 if (dropdownY
< root
->GetScreenRectInAppUnits().y
) {
657 // Don't allow the drop-down to be placed above the top of the root frame.
664 nscoord below
= screen
.YMost() - dropdownY
;
665 nscoord above
= thisScreenRect
.y
+ aTranslation
->y
- minY
;
667 // If the difference between the space above and below is less
668 // than a row-height, then we favor the space below.
669 if (above
>= below
) {
670 nsListControlFrame
* lcf
= static_cast<nsListControlFrame
*>(mDropdownFrame
);
671 nscoord rowHeight
= lcf
->GetHeightOfARow();
672 if (above
< below
+ rowHeight
) {
681 nsComboboxControlFrame::DropDownPositionState
682 nsComboboxControlFrame::AbsolutelyPositionDropDown()
685 nscoord above
, below
;
686 GetAvailableDropdownSpace(&above
, &below
, &translation
);
687 if (above
<= 0 && below
<= 0) {
688 // Hide the view immediately to minimize flicker.
689 nsIView
* view
= mDropdownFrame
->GetView();
690 view
->GetViewManager()->SetViewVisibility(view
, nsViewVisibility_kHide
);
691 NS_DispatchToCurrentThread(new nsAsyncRollup(this));
692 return eDropDownPositionSuppressed
;
695 nsSize dropdownSize
= mDropdownFrame
->GetSize();
696 nscoord height
= NS_MAX(above
, below
);
697 nsListControlFrame
* lcf
= static_cast<nsListControlFrame
*>(mDropdownFrame
);
698 if (height
< dropdownSize
.height
) {
699 if (lcf
->GetNumDisplayRows() > 1) {
700 // The drop-down doesn't fit and currently shows more than 1 row -
701 // schedule a resize to show fewer rows.
702 NS_DispatchToCurrentThread(new nsAsyncResize(this));
703 return eDropDownPositionPendingResize
;
705 } else if (height
> (dropdownSize
.height
+ lcf
->GetHeightOfARow() * 1.5) &&
706 lcf
->GetDropdownCanGrow()) {
707 // The drop-down fits but there is room for at least 1.5 more rows -
708 // schedule a resize to show more rows if it has more rows to show.
709 // (1.5 rows for good measure to avoid any rounding issues that would
710 // lead to a loop of reflow requests)
711 NS_DispatchToCurrentThread(new nsAsyncResize(this));
712 return eDropDownPositionPendingResize
;
715 // Position the drop-down below if there is room, otherwise place it
716 // on the side that has more room.
717 bool b
= dropdownSize
.height
<= below
|| below
>= above
;
718 nsPoint
dropdownPosition(0, b
? GetRect().height
: -dropdownSize
.height
);
719 if (GetStyleVisibility()->mDirection
== NS_STYLE_DIRECTION_RTL
) {
720 // Align the right edge of the drop-down with the right edge of the control.
721 dropdownPosition
.x
= GetRect().width
- dropdownSize
.width
;
724 // Don't position the view unless the position changed since it might cause
725 // a call to NotifyGeometryChange() and an infinite loop here.
726 const nsPoint currentPos
= mDropdownFrame
->GetPosition();
727 const nsPoint newPos
= dropdownPosition
+ translation
;
728 if (currentPos
!= newPos
) {
729 mDropdownFrame
->SetPosition(newPos
);
730 nsContainerFrame::PositionFrameView(mDropdownFrame
);
732 return eDropDownPositionFinal
;
736 nsComboboxControlFrame::NotifyGeometryChange()
738 // We don't need to resize if we're not dropped down since ShowDropDown
739 // does that, or if we're dirty then the reflow callback does it,
740 // or if we have a delayed ShowDropDown pending.
741 if (IsDroppedDown() &&
742 !(GetStateBits() & NS_FRAME_IS_DIRTY
) &&
743 !mDelayedShowDropDown
) {
744 // Async because we're likely in a middle of a scroll here so
745 // frame/view positions are in flux.
746 nsRefPtr
<nsResizeDropdownAtFinalPosition
> resize
=
747 new nsResizeDropdownAtFinalPosition(this);
748 NS_DispatchToCurrentThread(resize
);
752 //----------------------------------------------------------
754 //----------------------------------------------------------
755 #ifdef DO_REFLOW_DEBUG
756 static int myCounter
= 0;
758 static void printSize(char * aDesc
, nscoord aSize
)
760 printf(" %s: ", aDesc
);
761 if (aSize
== NS_UNCONSTRAINEDSIZE
) {
764 printf("%d", PX(aSize
));
769 //-------------------------------------------------------------------
770 //-- Main Reflow for the Combobox
771 //-------------------------------------------------------------------
774 nsComboboxControlFrame::GetIntrinsicWidth(nsRenderingContext
* aRenderingContext
,
775 nsLayoutUtils::IntrinsicWidthType aType
)
777 // get the scrollbar width, we'll use this later
778 nscoord scrollbarWidth
= 0;
779 nsPresContext
* presContext
= PresContext();
780 if (mListControlFrame
) {
781 nsIScrollableFrame
* scrollable
= do_QueryFrame(mListControlFrame
);
782 NS_ASSERTION(scrollable
, "List must be a scrollable frame");
784 scrollable
->GetDesiredScrollbarSizes(presContext
, aRenderingContext
).LeftRight();
787 nscoord displayWidth
= 0;
788 if (NS_LIKELY(mDisplayFrame
)) {
789 displayWidth
= nsLayoutUtils::IntrinsicForContainer(aRenderingContext
,
794 if (mDropdownFrame
) {
795 nscoord dropdownContentWidth
;
796 if (aType
== nsLayoutUtils::MIN_WIDTH
) {
797 dropdownContentWidth
= mDropdownFrame
->GetMinWidth(aRenderingContext
);
799 NS_ASSERTION(aType
== nsLayoutUtils::PREF_WIDTH
, "Unexpected type");
800 dropdownContentWidth
= mDropdownFrame
->GetPrefWidth(aRenderingContext
);
802 dropdownContentWidth
= NSCoordSaturatingSubtract(dropdownContentWidth
,
806 displayWidth
= NS_MAX(dropdownContentWidth
, displayWidth
);
809 // add room for the dropmarker button if there is one
810 if (!IsThemed() || presContext
->GetTheme()->ThemeNeedsComboboxDropmarker())
811 displayWidth
+= scrollbarWidth
;
818 nsComboboxControlFrame::GetMinWidth(nsRenderingContext
*aRenderingContext
)
821 DISPLAY_MIN_WIDTH(this, minWidth
);
822 minWidth
= GetIntrinsicWidth(aRenderingContext
, nsLayoutUtils::MIN_WIDTH
);
827 nsComboboxControlFrame::GetPrefWidth(nsRenderingContext
*aRenderingContext
)
830 DISPLAY_PREF_WIDTH(this, prefWidth
);
831 prefWidth
= GetIntrinsicWidth(aRenderingContext
, nsLayoutUtils::PREF_WIDTH
);
836 nsComboboxControlFrame::Reflow(nsPresContext
* aPresContext
,
837 nsHTMLReflowMetrics
& aDesiredSize
,
838 const nsHTMLReflowState
& aReflowState
,
839 nsReflowStatus
& aStatus
)
841 // Constraints we try to satisfy:
843 // 1) Default width of button is the vertical scrollbar size
844 // 2) If the width of button is bigger than our width, set width of
846 // 3) Default height of button is height of display area
847 // 4) Width of display area is whatever is left over from our width after
848 // allocating width for the button.
849 // 5) Height of display area is GetHeightOfARow() on the
850 // mListControlFrame.
852 if (!mDisplayFrame
|| !mButtonFrame
|| !mDropdownFrame
) {
853 NS_ERROR("Why did the frame constructor allow this to happen? Fix it!!");
854 return NS_ERROR_UNEXPECTED
;
857 // Make sure the displayed text is the same as the selected option, bug 297389.
858 int32_t selectedIndex
;
859 nsAutoString selectedOptionText
;
861 selectedIndex
= mListControlFrame
->GetSelectedIndex();
864 // In dropped down mode the "selected index" is the hovered menu item,
865 // we want the last selected item which is |mDisplayedIndex| in this case.
866 selectedIndex
= mDisplayedIndex
;
868 if (selectedIndex
!= -1) {
869 mListControlFrame
->GetOptionText(selectedIndex
, selectedOptionText
);
871 if (mDisplayedOptionText
!= selectedOptionText
) {
872 RedisplayText(selectedIndex
);
875 // First reflow our dropdown so that we know how tall we should be.
876 ReflowDropdown(aPresContext
, aReflowState
);
877 nsRefPtr
<nsResizeDropdownAtFinalPosition
> resize
=
878 new nsResizeDropdownAtFinalPosition(this);
879 if (NS_SUCCEEDED(aPresContext
->PresShell()->PostReflowCallback(resize
))) {
880 // The reflow callback queue doesn't AddRef so we keep it alive until
881 // it's released in its ReflowFinished / ReflowCallbackCanceled.
885 // Get the width of the vertical scrollbar. That will be the width of the
888 const nsStyleDisplay
*disp
= GetStyleDisplay();
889 if (IsThemed(disp
) && !aPresContext
->GetTheme()->ThemeNeedsComboboxDropmarker()) {
893 nsIScrollableFrame
* scrollable
= do_QueryFrame(mListControlFrame
);
894 NS_ASSERTION(scrollable
, "List must be a scrollable frame");
896 scrollable
->GetDesiredScrollbarSizes(PresContext(),
897 aReflowState
.rendContext
).LeftRight();
898 if (buttonWidth
> aReflowState
.ComputedWidth()) {
903 mDisplayWidth
= aReflowState
.ComputedWidth() - buttonWidth
;
905 nsresult rv
= nsBlockFrame::Reflow(aPresContext
, aDesiredSize
, aReflowState
,
907 NS_ENSURE_SUCCESS(rv
, rv
);
909 // Now set the correct width and height on our button. The width we need to
910 // set always, the height only if we had an auto height.
911 nsRect buttonRect
= mButtonFrame
->GetRect();
912 // If we have a non-intrinsic computed height, our kids should have sized
913 // themselves properly on their own.
914 if (aReflowState
.ComputedHeight() == NS_INTRINSICSIZE
) {
915 // The display frame is going to be the right height and width at this
916 // point. Use its height as the button height.
917 nsRect displayRect
= mDisplayFrame
->GetRect();
918 buttonRect
.height
= displayRect
.height
;
919 buttonRect
.y
= displayRect
.y
;
923 nscoord buttonHeight
= mButtonFrame
->GetSize().height
;
924 nscoord displayHeight
= mDisplayFrame
->GetSize().height
;
926 // The button and display area should be equal heights, unless the computed
927 // height on the combobox is too small to fit their borders and padding.
928 NS_ASSERTION(buttonHeight
== displayHeight
||
929 (aReflowState
.ComputedHeight() < buttonHeight
&&
931 mButtonFrame
->GetUsedBorderAndPadding().TopBottom()) ||
932 (aReflowState
.ComputedHeight() < displayHeight
&&
934 mDisplayFrame
->GetUsedBorderAndPadding().TopBottom()),
935 "Different heights?");
939 if (GetStyleVisibility()->mDirection
== NS_STYLE_DIRECTION_RTL
) {
940 // Make sure the right edge of the button frame stays where it is now
941 buttonRect
.x
-= buttonWidth
- buttonRect
.width
;
943 buttonRect
.width
= buttonWidth
;
944 mButtonFrame
->SetRect(buttonRect
);
949 //--------------------------------------------------------------
952 nsComboboxControlFrame::GetType() const
954 return nsGkAtoms::comboboxControlFrame
;
959 nsComboboxControlFrame::GetFrameName(nsAString
& aResult
) const
961 return MakeFrameName(NS_LITERAL_STRING("ComboboxControl"), aResult
);
966 //----------------------------------------------------------------------
967 // nsIComboboxControlFrame
968 //----------------------------------------------------------------------
970 nsComboboxControlFrame::ShowDropDown(bool aDoDropDown
)
972 mDelayedShowDropDown
= false;
973 nsEventStates eventStates
= mContent
->AsElement()->State();
974 if (aDoDropDown
&& eventStates
.HasState(NS_EVENT_STATE_DISABLED
)) {
978 if (!mDroppedDown
&& aDoDropDown
) {
979 if (sFocused
== this) {
980 DropDownPositionState state
= AbsolutelyPositionDropDown();
981 if (state
== eDropDownPositionFinal
) {
982 ShowList(aDoDropDown
); // might destroy us
983 } else if (state
== eDropDownPositionPendingResize
) {
984 // Delay until after the resize reflow, see nsAsyncResize.
985 mDelayedShowDropDown
= true;
988 // Delay until we get focus, see SetFocus().
989 mDelayedShowDropDown
= true;
991 } else if (mDroppedDown
&& !aDoDropDown
) {
992 ShowList(aDoDropDown
); // might destroy us
997 nsComboboxControlFrame::SetDropDown(nsIFrame
* aDropDownFrame
)
999 mDropdownFrame
= aDropDownFrame
;
1000 mListControlFrame
= do_QueryFrame(mDropdownFrame
);
1004 nsComboboxControlFrame::GetDropDown()
1006 return mDropdownFrame
;
1009 ///////////////////////////////////////////////////////////////
1012 nsComboboxControlFrame::RedisplaySelectedText()
1014 nsAutoScriptBlocker scriptBlocker
;
1015 return RedisplayText(mListControlFrame
->GetSelectedIndex());
1019 nsComboboxControlFrame::RedisplayText(int32_t aIndex
)
1021 // Get the text to display
1023 mListControlFrame
->GetOptionText(aIndex
, mDisplayedOptionText
);
1025 mDisplayedOptionText
.Truncate();
1027 mDisplayedIndex
= aIndex
;
1029 REFLOW_DEBUG_MSG2("RedisplayText \"%s\"\n",
1030 NS_LossyConvertUTF16toASCII(mDisplayedOptionText
).get());
1032 // Send reflow command because the new text maybe larger
1033 nsresult rv
= NS_OK
;
1034 if (mDisplayContent
) {
1035 // Don't call ActuallyDisplayText(true) directly here since that
1036 // could cause recursive frame construction. See bug 283117 and the comment in
1037 // HandleRedisplayTextEvent() below.
1039 // Revoke outstanding events to avoid out-of-order events which could mean
1040 // displaying the wrong text.
1041 mRedisplayTextEvent
.Revoke();
1043 NS_ASSERTION(!nsContentUtils::IsSafeToRunScript(),
1044 "If we happen to run our redisplay event now, we might kill "
1047 nsRefPtr
<RedisplayTextEvent
> event
= new RedisplayTextEvent(this);
1048 mRedisplayTextEvent
= event
;
1049 if (!nsContentUtils::AddScriptRunner(event
))
1050 mRedisplayTextEvent
.Forget();
1056 nsComboboxControlFrame::HandleRedisplayTextEvent()
1058 // First, make sure that the content model is up to date and we've
1059 // constructed the frames for all our content in the right places.
1060 // Otherwise they'll end up under the wrong insertion frame when we
1061 // ActuallyDisplayText, since that flushes out the content sink by
1062 // calling SetText on a DOM node with aNotify set to true. See bug
1064 nsWeakFrame
weakThis(this);
1065 PresContext()->Document()->
1066 FlushPendingNotifications(Flush_ContentAndNotify
);
1067 if (!weakThis
.IsAlive())
1070 // Redirect frame insertions during this method (see GetContentInsertionFrame())
1071 // so that any reframing that the frame constructor forces upon us is inserted
1072 // into the correct parent (mDisplayFrame). See bug 282607.
1073 NS_PRECONDITION(!mInRedisplayText
, "Nested RedisplayText");
1074 mInRedisplayText
= true;
1075 mRedisplayTextEvent
.Forget();
1077 ActuallyDisplayText(true);
1078 // XXXbz This should perhaps be eResize. Check.
1079 PresContext()->PresShell()->FrameNeedsReflow(mDisplayFrame
,
1080 nsIPresShell::eStyleChange
,
1083 mInRedisplayText
= false;
1087 nsComboboxControlFrame::ActuallyDisplayText(bool aNotify
)
1089 if (mDisplayedOptionText
.IsEmpty()) {
1090 // Have to use a non-breaking space for line-height calculations
1092 static const PRUnichar space
= 0xA0;
1093 mDisplayContent
->SetText(&space
, 1, aNotify
);
1095 mDisplayContent
->SetText(mDisplayedOptionText
, aNotify
);
1100 nsComboboxControlFrame::GetIndexOfDisplayArea()
1102 return mDisplayedIndex
;
1105 //----------------------------------------------------------------------
1106 // nsISelectControlFrame
1107 //----------------------------------------------------------------------
1109 nsComboboxControlFrame::DoneAddingChildren(bool aIsDone
)
1111 nsISelectControlFrame
* listFrame
= do_QueryFrame(mDropdownFrame
);
1113 return NS_ERROR_FAILURE
;
1115 return listFrame
->DoneAddingChildren(aIsDone
);
1119 nsComboboxControlFrame::AddOption(int32_t aIndex
)
1121 if (aIndex
<= mDisplayedIndex
) {
1125 nsListControlFrame
* lcf
= static_cast<nsListControlFrame
*>(mDropdownFrame
);
1126 return lcf
->AddOption(aIndex
);
1131 nsComboboxControlFrame::RemoveOption(int32_t aIndex
)
1133 nsWeakFrame
weakThis(this);
1134 if (mListControlFrame
->GetNumberOfOptions() > 0) {
1135 if (aIndex
< mDisplayedIndex
) {
1137 } else if (aIndex
== mDisplayedIndex
) {
1138 mDisplayedIndex
= 0; // IE6 compat
1139 RedisplayText(mDisplayedIndex
);
1143 // If we removed the last option, we need to blank things out
1147 if (!weakThis
.IsAlive())
1150 nsListControlFrame
* lcf
= static_cast<nsListControlFrame
*>(mDropdownFrame
);
1151 return lcf
->RemoveOption(aIndex
);
1155 nsComboboxControlFrame::OnSetSelectedIndex(int32_t aOldIndex
, int32_t aNewIndex
)
1157 nsAutoScriptBlocker scriptBlocker
;
1158 RedisplayText(aNewIndex
);
1159 NS_ASSERTION(mDropdownFrame
, "No dropdown frame!");
1161 nsISelectControlFrame
* listFrame
= do_QueryFrame(mDropdownFrame
);
1162 NS_ASSERTION(listFrame
, "No list frame!");
1164 return listFrame
->OnSetSelectedIndex(aOldIndex
, aNewIndex
);
1167 // End nsISelectControlFrame
1168 //----------------------------------------------------------------------
1171 nsComboboxControlFrame::HandleEvent(nsPresContext
* aPresContext
,
1173 nsEventStatus
* aEventStatus
)
1175 NS_ENSURE_ARG_POINTER(aEventStatus
);
1177 if (nsEventStatus_eConsumeNoDefault
== *aEventStatus
) {
1181 nsEventStates eventStates
= mContent
->AsElement()->State();
1182 if (eventStates
.HasState(NS_EVENT_STATE_DISABLED
)) {
1186 // If we have style that affects how we are selected, feed event down to
1187 // nsFrame::HandleEvent so that selection takes place when appropriate.
1188 const nsStyleUserInterface
* uiStyle
= GetStyleUserInterface();
1189 if (uiStyle
->mUserInput
== NS_STYLE_USER_INPUT_NONE
|| uiStyle
->mUserInput
== NS_STYLE_USER_INPUT_DISABLED
)
1190 return nsBlockFrame::HandleEvent(aPresContext
, aEvent
, aEventStatus
);
1197 nsComboboxControlFrame::SetFormProperty(nsIAtom
* aName
, const nsAString
& aValue
)
1199 nsIFormControlFrame
* fcFrame
= do_QueryFrame(mDropdownFrame
);
1201 return NS_NOINTERFACE
;
1204 return fcFrame
->SetFormProperty(aName
, aValue
);
1208 nsComboboxControlFrame::GetFormProperty(nsIAtom
* aName
, nsAString
& aValue
) const
1210 nsIFormControlFrame
* fcFrame
= do_QueryFrame(mDropdownFrame
);
1212 return NS_ERROR_FAILURE
;
1215 return fcFrame
->GetFormProperty(aName
, aValue
);
1219 nsComboboxControlFrame::GetContentInsertionFrame() {
1220 return mInRedisplayText
? mDisplayFrame
: mDropdownFrame
->GetContentInsertionFrame();
1224 nsComboboxControlFrame::CreateAnonymousContent(nsTArray
<ContentInfo
>& aElements
)
1226 // The frames used to display the combo box and the button used to popup the dropdown list
1227 // are created through anonymous content. The dropdown list is not created through anonymous
1228 // content because it's frame is initialized specifically for the drop-down case and it is placed
1229 // a special list referenced through NS_COMBO_FRAME_POPUP_LIST_INDEX to keep separate from the
1230 // layout of the display and button.
1232 // Note: The value attribute of the display content is set when an item is selected in the dropdown list.
1233 // If the content specified below does not honor the value attribute than nothing will be displayed.
1235 // For now the content that is created corresponds to two input buttons. It would be better to create the
1236 // tag as something other than input, but then there isn't any way to create a button frame since it
1237 // isn't possible to set the display type in CSS2 to create a button frame.
1239 // create content used for display
1240 //nsIAtom* tag = NS_NewAtom("mozcombodisplay");
1242 // Add a child text content node for the label
1244 nsNodeInfoManager
*nimgr
= mContent
->NodeInfo()->NodeInfoManager();
1246 NS_NewTextNode(getter_AddRefs(mDisplayContent
), nimgr
);
1247 if (!mDisplayContent
)
1248 return NS_ERROR_OUT_OF_MEMORY
;
1250 // set the value of the text node
1251 mDisplayedIndex
= mListControlFrame
->GetSelectedIndex();
1252 if (mDisplayedIndex
!= -1) {
1253 mListControlFrame
->GetOptionText(mDisplayedIndex
, mDisplayedOptionText
);
1255 ActuallyDisplayText(false);
1257 if (!aElements
.AppendElement(mDisplayContent
))
1258 return NS_ERROR_OUT_OF_MEMORY
;
1260 nsCOMPtr
<nsINodeInfo
> nodeInfo
;
1261 nodeInfo
= nimgr
->GetNodeInfo(nsGkAtoms::button
, nullptr, kNameSpaceID_XHTML
,
1262 nsIDOMNode::ELEMENT_NODE
);
1264 // create button which drops the list down
1265 NS_NewHTMLElement(getter_AddRefs(mButtonContent
), nodeInfo
.forget(),
1266 dom::NOT_FROM_PARSER
);
1267 if (!mButtonContent
)
1268 return NS_ERROR_OUT_OF_MEMORY
;
1270 // make someone to listen to the button. If its pressed by someone like Accessibility
1271 // then open or close the combo box.
1272 mButtonListener
= new nsComboButtonListener(this);
1273 mButtonContent
->AddEventListener(NS_LITERAL_STRING("click"), mButtonListener
,
1276 mButtonContent
->SetAttr(kNameSpaceID_None
, nsGkAtoms::type
,
1277 NS_LITERAL_STRING("button"), false);
1278 // Set tabindex="-1" so that the button is not tabbable
1279 mButtonContent
->SetAttr(kNameSpaceID_None
, nsGkAtoms::tabindex
,
1280 NS_LITERAL_STRING("-1"), false);
1282 if (!aElements
.AppendElement(mButtonContent
))
1283 return NS_ERROR_OUT_OF_MEMORY
;
1289 nsComboboxControlFrame::AppendAnonymousContentTo(nsBaseContentList
& aElements
,
1292 aElements
.MaybeAppendElement(mDisplayContent
);
1293 aElements
.MaybeAppendElement(mButtonContent
);
1296 // XXXbz this is a for-now hack. Now that display:inline-block works,
1297 // need to revisit this.
1298 class nsComboboxDisplayFrame
: public nsBlockFrame
{
1300 NS_DECL_FRAMEARENA_HELPERS
1302 nsComboboxDisplayFrame (nsStyleContext
* aContext
,
1303 nsComboboxControlFrame
* aComboBox
)
1304 : nsBlockFrame(aContext
),
1305 mComboBox(aComboBox
)
1308 // Need this so that line layout knows that this block's width
1309 // depends on the available width.
1310 virtual nsIAtom
* GetType() const;
1312 virtual bool IsFrameOfType(uint32_t aFlags
) const
1314 return nsBlockFrame::IsFrameOfType(aFlags
&
1315 ~(nsIFrame::eReplacedContainsBlock
));
1318 NS_IMETHOD
Reflow(nsPresContext
* aPresContext
,
1319 nsHTMLReflowMetrics
& aDesiredSize
,
1320 const nsHTMLReflowState
& aReflowState
,
1321 nsReflowStatus
& aStatus
);
1323 NS_IMETHOD
BuildDisplayList(nsDisplayListBuilder
* aBuilder
,
1324 const nsRect
& aDirtyRect
,
1325 const nsDisplayListSet
& aLists
);
1328 nsComboboxControlFrame
* mComboBox
;
1331 NS_IMPL_FRAMEARENA_HELPERS(nsComboboxDisplayFrame
)
1334 nsComboboxDisplayFrame::GetType() const
1336 return nsGkAtoms::comboboxDisplayFrame
;
1340 nsComboboxDisplayFrame::Reflow(nsPresContext
* aPresContext
,
1341 nsHTMLReflowMetrics
& aDesiredSize
,
1342 const nsHTMLReflowState
& aReflowState
,
1343 nsReflowStatus
& aStatus
)
1345 nsHTMLReflowState
state(aReflowState
);
1346 if (state
.ComputedHeight() == NS_INTRINSICSIZE
) {
1347 // Note that the only way we can have a computed height here is if the
1348 // combobox had a specified height. If it didn't, size based on what our
1349 // rows look like, for lack of anything better.
1350 state
.SetComputedHeight(mComboBox
->mListControlFrame
->GetHeightOfARow());
1352 nscoord computedWidth
= mComboBox
->mDisplayWidth
-
1353 state
.mComputedBorderPadding
.LeftRight();
1354 if (computedWidth
< 0) {
1357 state
.SetComputedWidth(computedWidth
);
1359 return nsBlockFrame::Reflow(aPresContext
, aDesiredSize
, state
, aStatus
);
1363 nsComboboxDisplayFrame::BuildDisplayList(nsDisplayListBuilder
* aBuilder
,
1364 const nsRect
& aDirtyRect
,
1365 const nsDisplayListSet
& aLists
)
1367 nsDisplayListCollection set
;
1368 nsresult rv
= nsBlockFrame::BuildDisplayList(aBuilder
, aDirtyRect
, set
);
1372 // remove background items if parent frame is themed
1373 if (mComboBox
->IsThemed()) {
1374 set
.BorderBackground()->DeleteAll();
1383 nsComboboxControlFrame::CreateFrameFor(nsIContent
* aContent
)
1385 NS_PRECONDITION(nullptr != aContent
, "null ptr");
1387 NS_ASSERTION(mDisplayContent
, "mDisplayContent can't be null!");
1389 if (mDisplayContent
!= aContent
) {
1390 // We only handle the frames for mDisplayContent here
1395 nsIPresShell
*shell
= PresContext()->PresShell();
1396 nsStyleSet
*styleSet
= shell
->StyleSet();
1398 // create the style contexts for the anonymous block frame and text frame
1399 nsRefPtr
<nsStyleContext
> styleContext
;
1400 styleContext
= styleSet
->
1401 ResolveAnonymousBoxStyle(nsCSSAnonBoxes::mozDisplayComboboxControlFrame
,
1403 if (NS_UNLIKELY(!styleContext
)) {
1407 nsRefPtr
<nsStyleContext
> textStyleContext
;
1408 textStyleContext
= styleSet
->ResolveStyleForNonElement(mStyleContext
);
1409 if (NS_UNLIKELY(!textStyleContext
)) {
1413 // Start by by creating our anonymous block frame
1414 mDisplayFrame
= new (shell
) nsComboboxDisplayFrame(styleContext
, this);
1415 if (NS_UNLIKELY(!mDisplayFrame
)) {
1419 nsresult rv
= mDisplayFrame
->Init(mContent
, this, nullptr);
1420 if (NS_FAILED(rv
)) {
1421 mDisplayFrame
->Destroy();
1422 mDisplayFrame
= nullptr;
1426 // Create a text frame and put it inside the block frame
1427 nsIFrame
* textFrame
= NS_NewTextFrame(shell
, textStyleContext
);
1428 if (NS_UNLIKELY(!textFrame
)) {
1432 // initialize the text frame
1433 rv
= textFrame
->Init(aContent
, mDisplayFrame
, nullptr);
1434 if (NS_FAILED(rv
)) {
1435 mDisplayFrame
->Destroy();
1436 mDisplayFrame
= nullptr;
1437 textFrame
->Destroy();
1438 textFrame
= nullptr;
1441 mDisplayContent
->SetPrimaryFrame(textFrame
);
1443 nsFrameList
textList(textFrame
, textFrame
);
1444 mDisplayFrame
->SetInitialChildList(kPrincipalList
, textList
);
1445 return mDisplayFrame
;
1449 nsComboboxControlFrame::DestroyFrom(nsIFrame
* aDestructRoot
)
1451 // Revoke any pending RedisplayTextEvent
1452 mRedisplayTextEvent
.Revoke();
1454 nsFormControlFrame::RegUnRegAccessKey(static_cast<nsIFrame
*>(this), false);
1458 nsIFrame
* listFrame
= do_QueryFrame(mListControlFrame
);
1460 nsIView
* view
= listFrame
->GetView();
1461 NS_ASSERTION(view
, "nsComboboxControlFrame view is null");
1463 nsIWidget
* widget
= view
->GetWidget();
1465 widget
->CaptureRollupEvents(this, false, true);
1470 // Cleanup frames in popup child list
1471 mPopupFrames
.DestroyFramesFrom(aDestructRoot
);
1472 nsContentUtils::DestroyAnonymousContent(&mDisplayContent
);
1473 nsContentUtils::DestroyAnonymousContent(&mButtonContent
);
1474 nsBlockFrame::DestroyFrom(aDestructRoot
);
1478 nsComboboxControlFrame::GetChildList(ChildListID aListID
) const
1480 if (kSelectPopupList
== aListID
) {
1481 return mPopupFrames
;
1483 return nsBlockFrame::GetChildList(aListID
);
1487 nsComboboxControlFrame::GetChildLists(nsTArray
<ChildList
>* aLists
) const
1489 nsBlockFrame::GetChildLists(aLists
);
1490 mPopupFrames
.AppendIfNonempty(aLists
, kSelectPopupList
);
1494 nsComboboxControlFrame::SetInitialChildList(ChildListID aListID
,
1495 nsFrameList
& aChildList
)
1497 nsresult rv
= NS_OK
;
1498 if (kSelectPopupList
== aListID
) {
1499 mPopupFrames
.SetFrames(aChildList
);
1501 for (nsFrameList::Enumerator
e(aChildList
); !e
.AtEnd(); e
.Next()) {
1502 nsCOMPtr
<nsIFormControl
> formControl
=
1503 do_QueryInterface(e
.get()->GetContent());
1504 if (formControl
&& formControl
->GetType() == NS_FORM_BUTTON_BUTTON
) {
1505 mButtonFrame
= e
.get();
1509 NS_ASSERTION(mButtonFrame
, "missing button frame in initial child list");
1510 rv
= nsBlockFrame::SetInitialChildList(aListID
, aChildList
);
1515 //----------------------------------------------------------------------
1517 //----------------------------------------------------------------------
1519 nsComboboxControlFrame::Rollup(uint32_t aCount
, bool aGetLastRolledUp
)
1522 nsWeakFrame
weakFrame(this);
1523 mListControlFrame
->AboutToRollup(); // might destroy us
1524 if (!weakFrame
.IsAlive())
1526 ShowDropDown(false); // might destroy us
1527 if (!weakFrame
.IsAlive())
1529 mListControlFrame
->CaptureMouseEvents(false);
1536 nsComboboxControlFrame::RollupFromList()
1538 if (ShowList(false))
1539 mListControlFrame
->CaptureMouseEvents(false);
1543 nsComboboxControlFrame::UpdateRecentIndex(int32_t aIndex
)
1545 int32_t index
= mRecentSelectedIndex
;
1546 if (mRecentSelectedIndex
== NS_SKIP_NOTIFY_INDEX
|| aIndex
== NS_SKIP_NOTIFY_INDEX
)
1547 mRecentSelectedIndex
= aIndex
;
1551 class nsDisplayComboboxFocus
: public nsDisplayItem
{
1553 nsDisplayComboboxFocus(nsDisplayListBuilder
* aBuilder
,
1554 nsComboboxControlFrame
* aFrame
)
1555 : nsDisplayItem(aBuilder
, aFrame
) {
1556 MOZ_COUNT_CTOR(nsDisplayComboboxFocus
);
1558 #ifdef NS_BUILD_REFCNT_LOGGING
1559 virtual ~nsDisplayComboboxFocus() {
1560 MOZ_COUNT_DTOR(nsDisplayComboboxFocus
);
1564 virtual void Paint(nsDisplayListBuilder
* aBuilder
,
1565 nsRenderingContext
* aCtx
);
1566 NS_DISPLAY_DECL_NAME("ComboboxFocus", TYPE_COMBOBOX_FOCUS
)
1569 void nsDisplayComboboxFocus::Paint(nsDisplayListBuilder
* aBuilder
,
1570 nsRenderingContext
* aCtx
)
1572 static_cast<nsComboboxControlFrame
*>(mFrame
)
1573 ->PaintFocus(*aCtx
, ToReferenceFrame());
1577 nsComboboxControlFrame::BuildDisplayList(nsDisplayListBuilder
* aBuilder
,
1578 const nsRect
& aDirtyRect
,
1579 const nsDisplayListSet
& aLists
)
1582 printf("%p paint at (%d, %d, %d, %d)\n", this,
1583 aDirtyRect
.x
, aDirtyRect
.y
, aDirtyRect
.width
, aDirtyRect
.height
);
1586 if (aBuilder
->IsForEventDelivery()) {
1587 // Don't allow children to receive events.
1588 // REVIEW: following old GetFrameForPoint
1589 nsresult rv
= DisplayBorderBackgroundOutline(aBuilder
, aLists
);
1590 NS_ENSURE_SUCCESS(rv
, rv
);
1592 // REVIEW: Our in-flow child frames are inline-level so they will paint in our
1593 // content list, so we don't need to mess with layers.
1594 nsresult rv
= nsBlockFrame::BuildDisplayList(aBuilder
, aDirtyRect
, aLists
);
1595 NS_ENSURE_SUCCESS(rv
, rv
);
1598 // draw a focus indicator only when focus rings should be drawn
1599 nsIDocument
* doc
= mContent
->GetCurrentDoc();
1601 nsPIDOMWindow
* window
= doc
->GetWindow();
1602 if (window
&& window
->ShouldShowFocusRing()) {
1603 nsPresContext
*presContext
= PresContext();
1604 const nsStyleDisplay
*disp
= GetStyleDisplay();
1605 if ((!IsThemed(disp
) ||
1606 !presContext
->GetTheme()->ThemeDrawsFocusForWidget(presContext
, this, disp
->mAppearance
)) &&
1607 mDisplayFrame
&& IsVisibleForPainting(aBuilder
)) {
1608 nsresult rv
= aLists
.Content()->AppendNewToTop(
1609 new (aBuilder
) nsDisplayComboboxFocus(aBuilder
, this));
1610 NS_ENSURE_SUCCESS(rv
, rv
);
1615 return DisplaySelectionOverlay(aBuilder
, aLists
.Content());
1618 void nsComboboxControlFrame::PaintFocus(nsRenderingContext
& aRenderingContext
,
1621 /* Do we need to do anything? */
1622 nsEventStates eventStates
= mContent
->AsElement()->State();
1623 if (eventStates
.HasState(NS_EVENT_STATE_DISABLED
) || sFocused
!= this)
1626 aRenderingContext
.PushState();
1627 nsRect clipRect
= mDisplayFrame
->GetRect() + aPt
;
1628 aRenderingContext
.IntersectClip(clipRect
);
1630 // REVIEW: Why does the old code paint mDisplayFrame again? We've
1631 // already painted it in the children above. So clipping it here won't do
1634 /////////////////////
1637 aRenderingContext
.SetLineStyle(nsLineStyle_kDotted
);
1638 aRenderingContext
.SetColor(GetStyleColor()->mColor
);
1640 //aRenderingContext.DrawRect(clipRect);
1642 nscoord onePixel
= nsPresContext::CSSPixelsToAppUnits(1);
1643 clipRect
.width
-= onePixel
;
1644 clipRect
.height
-= onePixel
;
1645 aRenderingContext
.DrawLine(clipRect
.TopLeft(), clipRect
.TopRight());
1646 aRenderingContext
.DrawLine(clipRect
.TopRight(), clipRect
.BottomRight());
1647 aRenderingContext
.DrawLine(clipRect
.BottomRight(), clipRect
.BottomLeft());
1648 aRenderingContext
.DrawLine(clipRect
.BottomLeft(), clipRect
.TopLeft());
1650 aRenderingContext
.PopState();
1653 //---------------------------------------------------------
1654 // gets the content (an option) by index and then set it as
1655 // being selected or not selected
1656 //---------------------------------------------------------
1658 nsComboboxControlFrame::OnOptionSelected(int32_t aIndex
, bool aSelected
)
1661 nsISelectControlFrame
*selectFrame
= do_QueryFrame(mListControlFrame
);
1663 selectFrame
->OnOptionSelected(aIndex
, aSelected
);
1667 nsAutoScriptBlocker blocker
;
1668 RedisplayText(aIndex
);
1670 nsWeakFrame
weakFrame(this);
1671 RedisplaySelectedText();
1672 if (weakFrame
.IsAlive()) {
1673 FireValueChangeEvent(); // Fire after old option is unselected
1681 void nsComboboxControlFrame::FireValueChangeEvent()
1683 // Fire ValueChange event to indicate data value of combo box has changed
1684 nsContentUtils::AddScriptRunner(
1685 new nsAsyncDOMEvent(mContent
, NS_LITERAL_STRING("ValueChange"), true,
1690 nsComboboxControlFrame::OnContentReset()
1692 if (mListControlFrame
) {
1693 mListControlFrame
->OnContentReset();
1698 //--------------------------------------------------------
1700 //--------------------------------------------------------
1702 nsComboboxControlFrame::SaveState(SpecialStateID aStateID
,
1703 nsPresState
** aState
)
1705 if (!mListControlFrame
)
1706 return NS_ERROR_FAILURE
;
1708 nsIStatefulFrame
* stateful
= do_QueryFrame(mListControlFrame
);
1709 return stateful
->SaveState(aStateID
, aState
);
1713 nsComboboxControlFrame::RestoreState(nsPresState
* aState
)
1715 if (!mListControlFrame
)
1716 return NS_ERROR_FAILURE
;
1718 nsIStatefulFrame
* stateful
= do_QueryFrame(mListControlFrame
);
1719 NS_ASSERTION(stateful
, "Must implement nsIStatefulFrame");
1720 return stateful
->RestoreState(aState
);
1725 // Camino uses a native widget for the combobox
1726 // popup, which affects drawing and event
1727 // handling here and in nsListControlFrame.
1729 // Also, Fennec use a custom combobox built-in widget
1734 nsComboboxControlFrame::ToolkitHasNativePopup()
1736 #ifdef MOZ_USE_NATIVE_POPUP_WINDOWS
1740 #endif /* MOZ_USE_NATIVE_POPUP_WINDOWS */