Backout a74bd5095902, Bug 959405 - Please update the Buri Moz-central, 1.3, 1.2 with...
[gecko.git] / layout / forms / nsComboboxControlFrame.cpp
blobfe00defc3c6b912dce203172dec9bb92910c3bbb
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 "nsINameSpaceManager.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 "nsIDOMNode.h"
21 #include "nsISelectControlFrame.h"
22 #include "nsContentUtils.h"
23 #include "nsIDocument.h"
24 #include "nsINodeInfo.h"
25 #include "nsIScrollableFrame.h"
26 #include "nsListControlFrame.h"
27 #include "nsAutoPtr.h"
28 #include "nsStyleSet.h"
29 #include "nsNodeInfoManager.h"
30 #include "nsContentCreatorFunctions.h"
31 #include "nsLayoutUtils.h"
32 #include "nsDisplayList.h"
33 #include "nsITheme.h"
34 #include "nsAsyncDOMEvent.h"
35 #include "nsRenderingContext.h"
36 #include "mozilla/Likely.h"
37 #include <algorithm>
38 #include "nsTextNode.h"
39 #include "mozilla/LookAndFeel.h"
40 #include "mozilla/MouseEvents.h"
42 using namespace mozilla;
44 NS_IMETHODIMP
45 nsComboboxControlFrame::RedisplayTextEvent::Run()
47 if (mControlFrame)
48 mControlFrame->HandleRedisplayTextEvent();
49 return NS_OK;
52 class nsPresState;
54 #define FIX_FOR_BUG_53259
56 // Drop down list event management.
57 // The combo box uses the following strategy for managing the drop-down list.
58 // If the combo box or it's arrow button is clicked on the drop-down list is displayed
59 // If mouse exit's the combo box with the drop-down list displayed the drop-down list
60 // is asked to capture events
61 // The drop-down list will capture all events including mouse down and up and will always
62 // return with ListWasSelected method call regardless of whether an item in the list was
63 // actually selected.
64 // The ListWasSelected code will turn off mouse-capture for the drop-down list.
65 // The drop-down list does not explicitly set capture when it is in the drop-down mode.
68 /**
69 * Helper class that listens to the combo boxes button. If the button is pressed the
70 * combo box is toggled to open or close. this is used by Accessibility which presses
71 * that button Programmatically.
73 class nsComboButtonListener : public nsIDOMEventListener
75 public:
76 NS_DECL_ISUPPORTS
78 NS_IMETHOD HandleEvent(nsIDOMEvent*)
80 mComboBox->ShowDropDown(!mComboBox->IsDroppedDown());
81 return NS_OK;
84 nsComboButtonListener(nsComboboxControlFrame* aCombobox)
86 mComboBox = aCombobox;
89 virtual ~nsComboButtonListener() {}
91 nsComboboxControlFrame* mComboBox;
94 NS_IMPL_ISUPPORTS1(nsComboButtonListener,
95 nsIDOMEventListener)
97 // static class data member for Bug 32920
98 nsComboboxControlFrame* nsComboboxControlFrame::sFocused = nullptr;
100 nsIFrame*
101 NS_NewComboboxControlFrame(nsIPresShell* aPresShell, nsStyleContext* aContext, uint32_t aStateFlags)
103 nsComboboxControlFrame* it = new (aPresShell) nsComboboxControlFrame(aContext);
105 if (it) {
106 // set the state flags (if any are provided)
107 it->AddStateBits(aStateFlags);
110 return it;
113 NS_IMPL_FRAMEARENA_HELPERS(nsComboboxControlFrame)
115 //-----------------------------------------------------------
116 // Reflow Debugging Macros
117 // These let us "see" how many reflow counts are happening
118 //-----------------------------------------------------------
119 #ifdef DO_REFLOW_COUNTER
121 #define MAX_REFLOW_CNT 1024
122 static int32_t gTotalReqs = 0;;
123 static int32_t gTotalReflows = 0;;
124 static int32_t gReflowControlCntRQ[MAX_REFLOW_CNT];
125 static int32_t gReflowControlCnt[MAX_REFLOW_CNT];
126 static int32_t gReflowInx = -1;
128 #define REFLOW_COUNTER() \
129 if (mReflowId > -1) \
130 gReflowControlCnt[mReflowId]++;
132 #define REFLOW_COUNTER_REQUEST() \
133 if (mReflowId > -1) \
134 gReflowControlCntRQ[mReflowId]++;
136 #define REFLOW_COUNTER_DUMP(__desc) \
137 if (mReflowId > -1) {\
138 gTotalReqs += gReflowControlCntRQ[mReflowId];\
139 gTotalReflows += gReflowControlCnt[mReflowId];\
140 printf("** Id:%5d %s RF: %d RQ: %d %d/%d %5.2f\n", \
141 mReflowId, (__desc), \
142 gReflowControlCnt[mReflowId], \
143 gReflowControlCntRQ[mReflowId],\
144 gTotalReflows, gTotalReqs, float(gTotalReflows)/float(gTotalReqs)*100.0f);\
147 #define REFLOW_COUNTER_INIT() \
148 if (gReflowInx < MAX_REFLOW_CNT) { \
149 gReflowInx++; \
150 mReflowId = gReflowInx; \
151 gReflowControlCnt[mReflowId] = 0; \
152 gReflowControlCntRQ[mReflowId] = 0; \
153 } else { \
154 mReflowId = -1; \
157 // reflow messages
158 #define REFLOW_DEBUG_MSG(_msg1) printf((_msg1))
159 #define REFLOW_DEBUG_MSG2(_msg1, _msg2) printf((_msg1), (_msg2))
160 #define REFLOW_DEBUG_MSG3(_msg1, _msg2, _msg3) printf((_msg1), (_msg2), (_msg3))
161 #define REFLOW_DEBUG_MSG4(_msg1, _msg2, _msg3, _msg4) printf((_msg1), (_msg2), (_msg3), (_msg4))
163 #else //-------------
165 #define REFLOW_COUNTER_REQUEST()
166 #define REFLOW_COUNTER()
167 #define REFLOW_COUNTER_DUMP(__desc)
168 #define REFLOW_COUNTER_INIT()
170 #define REFLOW_DEBUG_MSG(_msg)
171 #define REFLOW_DEBUG_MSG2(_msg1, _msg2)
172 #define REFLOW_DEBUG_MSG3(_msg1, _msg2, _msg3)
173 #define REFLOW_DEBUG_MSG4(_msg1, _msg2, _msg3, _msg4)
176 #endif
178 //------------------------------------------
179 // This is for being VERY noisy
180 //------------------------------------------
181 #ifdef DO_VERY_NOISY
182 #define REFLOW_NOISY_MSG(_msg1) printf((_msg1))
183 #define REFLOW_NOISY_MSG2(_msg1, _msg2) printf((_msg1), (_msg2))
184 #define REFLOW_NOISY_MSG3(_msg1, _msg2, _msg3) printf((_msg1), (_msg2), (_msg3))
185 #define REFLOW_NOISY_MSG4(_msg1, _msg2, _msg3, _msg4) printf((_msg1), (_msg2), (_msg3), (_msg4))
186 #else
187 #define REFLOW_NOISY_MSG(_msg)
188 #define REFLOW_NOISY_MSG2(_msg1, _msg2)
189 #define REFLOW_NOISY_MSG3(_msg1, _msg2, _msg3)
190 #define REFLOW_NOISY_MSG4(_msg1, _msg2, _msg3, _msg4)
191 #endif
193 //------------------------------------------
194 // Displays value in pixels or twips
195 //------------------------------------------
196 #ifdef DO_PIXELS
197 #define PX(__v) __v / 15
198 #else
199 #define PX(__v) __v
200 #endif
202 //------------------------------------------------------
203 //-- Done with macros
204 //------------------------------------------------------
206 nsComboboxControlFrame::nsComboboxControlFrame(nsStyleContext* aContext)
207 : nsBlockFrame(aContext)
208 , mDisplayFrame(nullptr)
209 , mButtonFrame(nullptr)
210 , mDropdownFrame(nullptr)
211 , mListControlFrame(nullptr)
212 , mDisplayWidth(0)
213 , mRecentSelectedIndex(NS_SKIP_NOTIFY_INDEX)
214 , mDisplayedIndex(-1)
215 , mLastDropDownAboveScreenY(nscoord_MIN)
216 , mLastDropDownBelowScreenY(nscoord_MIN)
217 , mDroppedDown(false)
218 , mInRedisplayText(false)
219 , mDelayedShowDropDown(false)
221 REFLOW_COUNTER_INIT()
224 //--------------------------------------------------------------
225 nsComboboxControlFrame::~nsComboboxControlFrame()
227 REFLOW_COUNTER_DUMP("nsCCF");
230 //--------------------------------------------------------------
232 NS_QUERYFRAME_HEAD(nsComboboxControlFrame)
233 NS_QUERYFRAME_ENTRY(nsIComboboxControlFrame)
234 NS_QUERYFRAME_ENTRY(nsIFormControlFrame)
235 NS_QUERYFRAME_ENTRY(nsIAnonymousContentCreator)
236 NS_QUERYFRAME_ENTRY(nsISelectControlFrame)
237 NS_QUERYFRAME_ENTRY(nsIStatefulFrame)
238 NS_QUERYFRAME_TAIL_INHERITING(nsBlockFrame)
240 #ifdef ACCESSIBILITY
241 a11y::AccType
242 nsComboboxControlFrame::AccessibleType()
244 return a11y::eHTMLComboboxType;
246 #endif
248 void
249 nsComboboxControlFrame::SetFocus(bool aOn, bool aRepaint)
251 nsWeakFrame weakFrame(this);
252 if (aOn) {
253 nsListControlFrame::ComboboxFocusSet();
254 sFocused = this;
255 if (mDelayedShowDropDown) {
256 ShowDropDown(true); // might destroy us
257 if (!weakFrame.IsAlive()) {
258 return;
261 } else {
262 sFocused = nullptr;
263 mDelayedShowDropDown = false;
264 if (mDroppedDown) {
265 mListControlFrame->ComboboxFinish(mDisplayedIndex); // might destroy us
266 if (!weakFrame.IsAlive()) {
267 return;
270 // May delete |this|.
271 mListControlFrame->FireOnChange();
274 if (!weakFrame.IsAlive()) {
275 return;
278 // This is needed on a temporary basis. It causes the focus
279 // rect to be drawn. This is much faster than ReResolvingStyle
280 // Bug 32920
281 InvalidateFrame();
284 void
285 nsComboboxControlFrame::ShowPopup(bool aShowPopup)
287 nsView* view = mDropdownFrame->GetView();
288 nsViewManager* viewManager = view->GetViewManager();
290 if (aShowPopup) {
291 nsRect rect = mDropdownFrame->GetRect();
292 rect.x = rect.y = 0;
293 viewManager->ResizeView(view, rect);
294 viewManager->SetViewVisibility(view, nsViewVisibility_kShow);
295 } else {
296 viewManager->SetViewVisibility(view, nsViewVisibility_kHide);
297 nsRect emptyRect(0, 0, 0, 0);
298 viewManager->ResizeView(view, emptyRect);
301 // fire a popup dom event
302 nsEventStatus status = nsEventStatus_eIgnore;
303 WidgetMouseEvent event(true, aShowPopup ?
304 NS_XUL_POPUP_SHOWING : NS_XUL_POPUP_HIDING, nullptr,
305 WidgetMouseEvent::eReal);
307 nsCOMPtr<nsIPresShell> shell = PresContext()->GetPresShell();
308 if (shell)
309 shell->HandleDOMEventWithTarget(mContent, &event, &status);
312 bool
313 nsComboboxControlFrame::ShowList(bool aShowList)
315 nsView* view = mDropdownFrame->GetView();
316 if (aShowList) {
317 NS_ASSERTION(!view->HasWidget(),
318 "We shouldn't have a widget before we need to display the popup");
320 // Create the widget for the drop-down list
321 view->GetViewManager()->SetViewFloating(view, true);
323 nsWidgetInitData widgetData;
324 widgetData.mWindowType = eWindowType_popup;
325 widgetData.mBorderStyle = eBorderStyle_default;
326 view->CreateWidgetForPopup(&widgetData);
327 } else {
328 nsIWidget* widget = view->GetWidget();
329 if (widget) {
330 // We must do this before ShowPopup in case it destroys us (bug 813442).
331 widget->CaptureRollupEvents(this, false);
335 nsWeakFrame weakFrame(this);
336 ShowPopup(aShowList); // might destroy us
337 if (!weakFrame.IsAlive()) {
338 return false;
341 mDroppedDown = aShowList;
342 nsIWidget* widget = view->GetWidget();
343 if (mDroppedDown) {
344 // The listcontrol frame will call back to the nsComboboxControlFrame's
345 // ListWasSelected which will stop the capture.
346 mListControlFrame->AboutToDropDown();
347 mListControlFrame->CaptureMouseEvents(true);
348 if (widget) {
349 widget->CaptureRollupEvents(this, true);
351 } else {
352 if (widget) {
353 view->DestroyWidget();
357 return weakFrame.IsAlive();
360 class nsResizeDropdownAtFinalPosition
361 : public nsIReflowCallback, public nsRunnable
363 public:
364 nsResizeDropdownAtFinalPosition(nsComboboxControlFrame* aFrame)
365 : mFrame(aFrame)
367 MOZ_COUNT_CTOR(nsResizeDropdownAtFinalPosition);
369 ~nsResizeDropdownAtFinalPosition()
371 MOZ_COUNT_DTOR(nsResizeDropdownAtFinalPosition);
374 virtual bool ReflowFinished()
376 Run();
377 NS_RELEASE_THIS();
378 return false;
381 virtual void ReflowCallbackCanceled()
383 NS_RELEASE_THIS();
386 NS_IMETHODIMP Run()
388 if (mFrame.IsAlive()) {
389 static_cast<nsComboboxControlFrame*>(mFrame.GetFrame())->
390 AbsolutelyPositionDropDown();
392 return NS_OK;
395 nsWeakFrame mFrame;
398 nsresult
399 nsComboboxControlFrame::ReflowDropdown(nsPresContext* aPresContext,
400 const nsHTMLReflowState& aReflowState)
402 // All we want out of it later on, really, is the height of a row, so we
403 // don't even need to cache mDropdownFrame's ascent or anything. If we don't
404 // need to reflow it, just bail out here.
405 if (!aReflowState.ShouldReflowAllKids() &&
406 !NS_SUBTREE_DIRTY(mDropdownFrame)) {
407 return NS_OK;
410 // XXXbz this will, for small-height dropdowns, have extra space on the right
411 // edge for the scrollbar we don't show... but that's the best we can do here
412 // for now.
413 nsSize availSize(aReflowState.AvailableWidth(), NS_UNCONSTRAINEDSIZE);
414 nsHTMLReflowState kidReflowState(aPresContext, aReflowState, mDropdownFrame,
415 availSize);
417 // If the dropdown's intrinsic width is narrower than our specified width,
418 // then expand it out. We want our border-box width to end up the same as
419 // the dropdown's so account for both sets of mComputedBorderPadding.
420 nscoord forcedWidth = aReflowState.ComputedWidth() +
421 aReflowState.ComputedPhysicalBorderPadding().LeftRight() -
422 kidReflowState.ComputedPhysicalBorderPadding().LeftRight();
423 kidReflowState.SetComputedWidth(std::max(kidReflowState.ComputedWidth(),
424 forcedWidth));
426 // ensure we start off hidden
427 if (GetStateBits() & NS_FRAME_FIRST_REFLOW) {
428 nsView* view = mDropdownFrame->GetView();
429 nsViewManager* viewManager = view->GetViewManager();
430 viewManager->SetViewVisibility(view, nsViewVisibility_kHide);
431 nsRect emptyRect(0, 0, 0, 0);
432 viewManager->ResizeView(view, emptyRect);
435 // Allow the child to move/size/change-visibility its view if it's currently
436 // dropped down
437 int32_t flags = NS_FRAME_NO_MOVE_FRAME | NS_FRAME_NO_VISIBILITY | NS_FRAME_NO_SIZE_VIEW;
438 if (mDroppedDown) {
439 flags = 0;
441 nsRect rect = mDropdownFrame->GetRect();
442 nsHTMLReflowMetrics desiredSize(aReflowState.GetWritingMode());
443 nsReflowStatus ignoredStatus;
444 nsresult rv = ReflowChild(mDropdownFrame, aPresContext, desiredSize,
445 kidReflowState, rect.x, rect.y, flags,
446 ignoredStatus);
448 // Set the child's width and height to it's desired size
449 FinishReflowChild(mDropdownFrame, aPresContext, &kidReflowState,
450 desiredSize, rect.x, rect.y, flags);
451 return rv;
454 nsPoint
455 nsComboboxControlFrame::GetCSSTransformTranslation()
457 nsIFrame* frame = this;
458 bool is3DTransform = false;
459 gfxMatrix transform;
460 while (frame) {
461 nsIFrame* parent;
462 gfx3DMatrix ctm = frame->GetTransformMatrix(nullptr, &parent);
463 gfxMatrix matrix;
464 if (ctm.Is2D(&matrix)) {
465 transform = transform * matrix;
466 } else {
467 is3DTransform = true;
468 break;
470 frame = parent;
472 nsPoint translation;
473 if (!is3DTransform && !transform.HasNonTranslation()) {
474 nsPresContext* pc = PresContext();
475 gfxPoint pixelTranslation = transform.GetTranslation();
476 int32_t apd = pc->AppUnitsPerDevPixel();
477 translation.x = NSFloatPixelsToAppUnits(float(pixelTranslation.x), apd);
478 translation.y = NSFloatPixelsToAppUnits(float(pixelTranslation.y), apd);
479 // To get the translation introduced only by transforms we subtract the
480 // regular non-transform translation.
481 nsRootPresContext* rootPC = pc->GetRootPresContext();
482 if (rootPC) {
483 translation -= GetOffsetToCrossDoc(rootPC->PresShell()->GetRootFrame());
484 } else {
485 translation.x = translation.y = 0;
488 return translation;
491 class nsAsyncRollup : public nsRunnable
493 public:
494 nsAsyncRollup(nsComboboxControlFrame* aFrame) : mFrame(aFrame) {}
495 NS_IMETHODIMP Run()
497 if (mFrame.IsAlive()) {
498 static_cast<nsComboboxControlFrame*>(mFrame.GetFrame())
499 ->RollupFromList();
501 return NS_OK;
503 nsWeakFrame mFrame;
506 class nsAsyncResize : public nsRunnable
508 public:
509 nsAsyncResize(nsComboboxControlFrame* aFrame) : mFrame(aFrame) {}
510 NS_IMETHODIMP Run()
512 if (mFrame.IsAlive()) {
513 nsComboboxControlFrame* combo =
514 static_cast<nsComboboxControlFrame*>(mFrame.GetFrame());
515 static_cast<nsListControlFrame*>(combo->mDropdownFrame)->
516 SetSuppressScrollbarUpdate(true);
517 nsCOMPtr<nsIPresShell> shell = mFrame->PresContext()->PresShell();
518 shell->FrameNeedsReflow(combo->mDropdownFrame, nsIPresShell::eResize,
519 NS_FRAME_IS_DIRTY);
520 shell->FlushPendingNotifications(Flush_Layout);
521 if (mFrame.IsAlive()) {
522 combo = static_cast<nsComboboxControlFrame*>(mFrame.GetFrame());
523 static_cast<nsListControlFrame*>(combo->mDropdownFrame)->
524 SetSuppressScrollbarUpdate(false);
525 if (combo->mDelayedShowDropDown) {
526 combo->ShowDropDown(true);
530 return NS_OK;
532 nsWeakFrame mFrame;
535 void
536 nsComboboxControlFrame::GetAvailableDropdownSpace(nscoord* aAbove,
537 nscoord* aBelow,
538 nsPoint* aTranslation)
540 // Note: At first glance, it appears that you could simply get the absolute
541 // bounding box for the dropdown list by first getting its view, then getting
542 // the view's nsIWidget, then asking the nsIWidget for its AbsoluteBounds.
543 // The problem with this approach, is that the dropdown lists y location can
544 // change based on whether the dropdown is placed below or above the display
545 // frame. The approach, taken here is to get the absolute position of the
546 // display frame and use its location to determine if the dropdown will go
547 // offscreen.
549 // Normal frame geometry (eg GetOffsetTo, mRect) doesn't include transforms.
550 // In the special case that our transform is only a 2D translation we
551 // introduce this hack so that the dropdown will show up in the right place.
552 *aTranslation = GetCSSTransformTranslation();
553 *aAbove = 0;
554 *aBelow = 0;
556 nsRect screen = nsFormControlFrame::GetUsableScreenRect(PresContext());
557 if (mLastDropDownBelowScreenY == nscoord_MIN) {
558 nsRect thisScreenRect = GetScreenRectInAppUnits();
559 mLastDropDownBelowScreenY = thisScreenRect.YMost() + aTranslation->y;
560 mLastDropDownAboveScreenY = thisScreenRect.y + aTranslation->y;
563 nscoord minY;
564 nsPresContext* pc = PresContext()->GetToplevelContentDocumentPresContext();
565 nsIFrame* root = pc ? pc->PresShell()->GetRootFrame() : nullptr;
566 if (root) {
567 minY = root->GetScreenRectInAppUnits().y;
568 if (mLastDropDownBelowScreenY < minY) {
569 // Don't allow the drop-down to be placed above the content area.
570 return;
572 } else {
573 minY = screen.y;
576 nscoord below = screen.YMost() - mLastDropDownBelowScreenY;
577 nscoord above = mLastDropDownAboveScreenY - minY;
579 // If the difference between the space above and below is less
580 // than a row-height, then we favor the space below.
581 if (above >= below) {
582 nsListControlFrame* lcf = static_cast<nsListControlFrame*>(mDropdownFrame);
583 nscoord rowHeight = lcf->GetHeightOfARow();
584 if (above < below + rowHeight) {
585 above -= rowHeight;
589 *aBelow = below;
590 *aAbove = above;
593 nsComboboxControlFrame::DropDownPositionState
594 nsComboboxControlFrame::AbsolutelyPositionDropDown()
596 nsPoint translation;
597 nscoord above, below;
598 mLastDropDownBelowScreenY = nscoord_MIN;
599 GetAvailableDropdownSpace(&above, &below, &translation);
600 if (above <= 0 && below <= 0) {
601 if (IsDroppedDown()) {
602 // Hide the view immediately to minimize flicker.
603 nsView* view = mDropdownFrame->GetView();
604 view->GetViewManager()->SetViewVisibility(view, nsViewVisibility_kHide);
605 NS_DispatchToCurrentThread(new nsAsyncRollup(this));
607 return eDropDownPositionSuppressed;
610 nsSize dropdownSize = mDropdownFrame->GetSize();
611 nscoord height = std::max(above, below);
612 nsListControlFrame* lcf = static_cast<nsListControlFrame*>(mDropdownFrame);
613 if (height < dropdownSize.height) {
614 if (lcf->GetNumDisplayRows() > 1) {
615 // The drop-down doesn't fit and currently shows more than 1 row -
616 // schedule a resize to show fewer rows.
617 NS_DispatchToCurrentThread(new nsAsyncResize(this));
618 return eDropDownPositionPendingResize;
620 } else if (height > (dropdownSize.height + lcf->GetHeightOfARow() * 1.5) &&
621 lcf->GetDropdownCanGrow()) {
622 // The drop-down fits but there is room for at least 1.5 more rows -
623 // schedule a resize to show more rows if it has more rows to show.
624 // (1.5 rows for good measure to avoid any rounding issues that would
625 // lead to a loop of reflow requests)
626 NS_DispatchToCurrentThread(new nsAsyncResize(this));
627 return eDropDownPositionPendingResize;
630 // Position the drop-down below if there is room, otherwise place it above
631 // if there is room. If there is no room for it on either side then place
632 // it below (to avoid overlapping UI like the URL bar).
633 bool b = dropdownSize.height <= below || dropdownSize.height > above;
634 nsPoint dropdownPosition(0, b ? GetRect().height : -dropdownSize.height);
635 if (StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL) {
636 // Align the right edge of the drop-down with the right edge of the control.
637 dropdownPosition.x = GetRect().width - dropdownSize.width;
640 // Don't position the view unless the position changed since it might cause
641 // a call to NotifyGeometryChange() and an infinite loop here.
642 const nsPoint currentPos = mDropdownFrame->GetPosition();
643 const nsPoint newPos = dropdownPosition + translation;
644 if (currentPos != newPos) {
645 mDropdownFrame->SetPosition(newPos);
646 nsContainerFrame::PositionFrameView(mDropdownFrame);
648 return eDropDownPositionFinal;
651 void
652 nsComboboxControlFrame::NotifyGeometryChange()
654 // We don't need to resize if we're not dropped down since ShowDropDown
655 // does that, or if we're dirty then the reflow callback does it,
656 // or if we have a delayed ShowDropDown pending.
657 if (IsDroppedDown() &&
658 !(GetStateBits() & NS_FRAME_IS_DIRTY) &&
659 !mDelayedShowDropDown) {
660 // Async because we're likely in a middle of a scroll here so
661 // frame/view positions are in flux.
662 nsRefPtr<nsResizeDropdownAtFinalPosition> resize =
663 new nsResizeDropdownAtFinalPosition(this);
664 NS_DispatchToCurrentThread(resize);
668 //----------------------------------------------------------
670 //----------------------------------------------------------
671 #ifdef DO_REFLOW_DEBUG
672 static int myCounter = 0;
674 static void printSize(char * aDesc, nscoord aSize)
676 printf(" %s: ", aDesc);
677 if (aSize == NS_UNCONSTRAINEDSIZE) {
678 printf("UC");
679 } else {
680 printf("%d", PX(aSize));
683 #endif
685 //-------------------------------------------------------------------
686 //-- Main Reflow for the Combobox
687 //-------------------------------------------------------------------
689 nscoord
690 nsComboboxControlFrame::GetIntrinsicWidth(nsRenderingContext* aRenderingContext,
691 nsLayoutUtils::IntrinsicWidthType aType)
693 // get the scrollbar width, we'll use this later
694 nscoord scrollbarWidth = 0;
695 nsPresContext* presContext = PresContext();
696 if (mListControlFrame) {
697 nsIScrollableFrame* scrollable = do_QueryFrame(mListControlFrame);
698 NS_ASSERTION(scrollable, "List must be a scrollable frame");
699 scrollbarWidth = scrollable->GetNondisappearingScrollbarWidth(
700 presContext, aRenderingContext);
703 nscoord displayWidth = 0;
704 if (MOZ_LIKELY(mDisplayFrame)) {
705 displayWidth = nsLayoutUtils::IntrinsicForContainer(aRenderingContext,
706 mDisplayFrame,
707 aType);
710 if (mDropdownFrame) {
711 nscoord dropdownContentWidth;
712 bool isUsingOverlayScrollbars =
713 LookAndFeel::GetInt(LookAndFeel::eIntID_UseOverlayScrollbars) != 0;
714 if (aType == nsLayoutUtils::MIN_WIDTH) {
715 dropdownContentWidth = mDropdownFrame->GetMinWidth(aRenderingContext);
716 if (isUsingOverlayScrollbars) {
717 dropdownContentWidth += scrollbarWidth;
719 } else {
720 NS_ASSERTION(aType == nsLayoutUtils::PREF_WIDTH, "Unexpected type");
721 dropdownContentWidth = mDropdownFrame->GetPrefWidth(aRenderingContext);
722 if (isUsingOverlayScrollbars) {
723 dropdownContentWidth += scrollbarWidth;
726 dropdownContentWidth = NSCoordSaturatingSubtract(dropdownContentWidth,
727 scrollbarWidth,
728 nscoord_MAX);
730 displayWidth = std::max(dropdownContentWidth, displayWidth);
733 // add room for the dropmarker button if there is one
734 if (!IsThemed() || presContext->GetTheme()->ThemeNeedsComboboxDropmarker())
735 displayWidth += scrollbarWidth;
737 return displayWidth;
741 nscoord
742 nsComboboxControlFrame::GetMinWidth(nsRenderingContext *aRenderingContext)
744 nscoord minWidth;
745 DISPLAY_MIN_WIDTH(this, minWidth);
746 minWidth = GetIntrinsicWidth(aRenderingContext, nsLayoutUtils::MIN_WIDTH);
747 return minWidth;
750 nscoord
751 nsComboboxControlFrame::GetPrefWidth(nsRenderingContext *aRenderingContext)
753 nscoord prefWidth;
754 DISPLAY_PREF_WIDTH(this, prefWidth);
755 prefWidth = GetIntrinsicWidth(aRenderingContext, nsLayoutUtils::PREF_WIDTH);
756 return prefWidth;
759 NS_IMETHODIMP
760 nsComboboxControlFrame::Reflow(nsPresContext* aPresContext,
761 nsHTMLReflowMetrics& aDesiredSize,
762 const nsHTMLReflowState& aReflowState,
763 nsReflowStatus& aStatus)
765 // Constraints we try to satisfy:
767 // 1) Default width of button is the vertical scrollbar size
768 // 2) If the width of button is bigger than our width, set width of
769 // button to 0.
770 // 3) Default height of button is height of display area
771 // 4) Width of display area is whatever is left over from our width after
772 // allocating width for the button.
773 // 5) Height of display area is GetHeightOfARow() on the
774 // mListControlFrame.
776 if (!mDisplayFrame || !mButtonFrame || !mDropdownFrame) {
777 NS_ERROR("Why did the frame constructor allow this to happen? Fix it!!");
778 return NS_ERROR_UNEXPECTED;
781 // Make sure the displayed text is the same as the selected option, bug 297389.
782 int32_t selectedIndex;
783 nsAutoString selectedOptionText;
784 if (!mDroppedDown) {
785 selectedIndex = mListControlFrame->GetSelectedIndex();
787 else {
788 // In dropped down mode the "selected index" is the hovered menu item,
789 // we want the last selected item which is |mDisplayedIndex| in this case.
790 selectedIndex = mDisplayedIndex;
792 if (selectedIndex != -1) {
793 mListControlFrame->GetOptionText(selectedIndex, selectedOptionText);
795 if (mDisplayedOptionText != selectedOptionText) {
796 RedisplayText(selectedIndex);
799 // First reflow our dropdown so that we know how tall we should be.
800 ReflowDropdown(aPresContext, aReflowState);
801 nsRefPtr<nsResizeDropdownAtFinalPosition> resize =
802 new nsResizeDropdownAtFinalPosition(this);
803 if (NS_SUCCEEDED(aPresContext->PresShell()->PostReflowCallback(resize))) {
804 // The reflow callback queue doesn't AddRef so we keep it alive until
805 // it's released in its ReflowFinished / ReflowCallbackCanceled.
806 resize.forget();
809 // Get the width of the vertical scrollbar. That will be the width of the
810 // dropdown button.
811 nscoord buttonWidth;
812 const nsStyleDisplay *disp = StyleDisplay();
813 if (IsThemed(disp) && !aPresContext->GetTheme()->ThemeNeedsComboboxDropmarker()) {
814 buttonWidth = 0;
816 else {
817 nsIScrollableFrame* scrollable = do_QueryFrame(mListControlFrame);
818 NS_ASSERTION(scrollable, "List must be a scrollable frame");
819 buttonWidth = scrollable->GetNondisappearingScrollbarWidth(
820 PresContext(), aReflowState.rendContext);
821 if (buttonWidth > aReflowState.ComputedWidth()) {
822 buttonWidth = 0;
826 mDisplayWidth = aReflowState.ComputedWidth() - buttonWidth;
828 nsresult rv = nsBlockFrame::Reflow(aPresContext, aDesiredSize, aReflowState,
829 aStatus);
830 NS_ENSURE_SUCCESS(rv, rv);
832 // Now set the correct width and height on our button. The width we need to
833 // set always, the height only if we had an auto height.
834 nsRect buttonRect = mButtonFrame->GetRect();
835 // If we have a non-intrinsic computed height, our kids should have sized
836 // themselves properly on their own.
837 if (aReflowState.ComputedHeight() == NS_INTRINSICSIZE) {
838 // The display frame is going to be the right height and width at this
839 // point. Use its height as the button height.
840 nsRect displayRect = mDisplayFrame->GetRect();
841 buttonRect.height = displayRect.height;
842 buttonRect.y = displayRect.y;
844 #ifdef DEBUG
845 else {
846 nscoord buttonHeight = mButtonFrame->GetSize().height;
847 nscoord displayHeight = mDisplayFrame->GetSize().height;
849 // The button and display area should be equal heights, unless the computed
850 // height on the combobox is too small to fit their borders and padding.
851 NS_ASSERTION(buttonHeight == displayHeight ||
852 (aReflowState.ComputedHeight() < buttonHeight &&
853 buttonHeight ==
854 mButtonFrame->GetUsedBorderAndPadding().TopBottom()) ||
855 (aReflowState.ComputedHeight() < displayHeight &&
856 displayHeight ==
857 mDisplayFrame->GetUsedBorderAndPadding().TopBottom()),
858 "Different heights?");
860 #endif
862 if (StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL) {
863 // Make sure the right edge of the button frame stays where it is now
864 buttonRect.x -= buttonWidth - buttonRect.width;
866 buttonRect.width = buttonWidth;
867 mButtonFrame->SetRect(buttonRect);
869 return rv;
872 //--------------------------------------------------------------
874 nsIAtom*
875 nsComboboxControlFrame::GetType() const
877 return nsGkAtoms::comboboxControlFrame;
880 #ifdef DEBUG_FRAME_DUMP
881 NS_IMETHODIMP
882 nsComboboxControlFrame::GetFrameName(nsAString& aResult) const
884 return MakeFrameName(NS_LITERAL_STRING("ComboboxControl"), aResult);
886 #endif
889 //----------------------------------------------------------------------
890 // nsIComboboxControlFrame
891 //----------------------------------------------------------------------
892 void
893 nsComboboxControlFrame::ShowDropDown(bool aDoDropDown)
895 mDelayedShowDropDown = false;
896 nsEventStates eventStates = mContent->AsElement()->State();
897 if (aDoDropDown && eventStates.HasState(NS_EVENT_STATE_DISABLED)) {
898 return;
901 if (!mDroppedDown && aDoDropDown) {
902 nsFocusManager* fm = nsFocusManager::GetFocusManager();
903 if (!fm || fm->GetFocusedContent() == GetContent()) {
904 DropDownPositionState state = AbsolutelyPositionDropDown();
905 if (state == eDropDownPositionFinal) {
906 ShowList(aDoDropDown); // might destroy us
907 } else if (state == eDropDownPositionPendingResize) {
908 // Delay until after the resize reflow, see nsAsyncResize.
909 mDelayedShowDropDown = true;
911 } else {
912 // Delay until we get focus, see SetFocus().
913 mDelayedShowDropDown = true;
915 } else if (mDroppedDown && !aDoDropDown) {
916 ShowList(aDoDropDown); // might destroy us
920 void
921 nsComboboxControlFrame::SetDropDown(nsIFrame* aDropDownFrame)
923 mDropdownFrame = aDropDownFrame;
924 mListControlFrame = do_QueryFrame(mDropdownFrame);
927 nsIFrame*
928 nsComboboxControlFrame::GetDropDown()
930 return mDropdownFrame;
933 ///////////////////////////////////////////////////////////////
935 NS_IMETHODIMP
936 nsComboboxControlFrame::RedisplaySelectedText()
938 nsAutoScriptBlocker scriptBlocker;
939 return RedisplayText(mListControlFrame->GetSelectedIndex());
942 nsresult
943 nsComboboxControlFrame::RedisplayText(int32_t aIndex)
945 // Get the text to display
946 if (aIndex != -1) {
947 mListControlFrame->GetOptionText(aIndex, mDisplayedOptionText);
948 } else {
949 mDisplayedOptionText.Truncate();
951 mDisplayedIndex = aIndex;
953 REFLOW_DEBUG_MSG2("RedisplayText \"%s\"\n",
954 NS_LossyConvertUTF16toASCII(mDisplayedOptionText).get());
956 // Send reflow command because the new text maybe larger
957 nsresult rv = NS_OK;
958 if (mDisplayContent) {
959 // Don't call ActuallyDisplayText(true) directly here since that
960 // could cause recursive frame construction. See bug 283117 and the comment in
961 // HandleRedisplayTextEvent() below.
963 // Revoke outstanding events to avoid out-of-order events which could mean
964 // displaying the wrong text.
965 mRedisplayTextEvent.Revoke();
967 NS_ASSERTION(!nsContentUtils::IsSafeToRunScript(),
968 "If we happen to run our redisplay event now, we might kill "
969 "ourselves!");
971 nsRefPtr<RedisplayTextEvent> event = new RedisplayTextEvent(this);
972 mRedisplayTextEvent = event;
973 if (!nsContentUtils::AddScriptRunner(event))
974 mRedisplayTextEvent.Forget();
976 return rv;
979 void
980 nsComboboxControlFrame::HandleRedisplayTextEvent()
982 // First, make sure that the content model is up to date and we've
983 // constructed the frames for all our content in the right places.
984 // Otherwise they'll end up under the wrong insertion frame when we
985 // ActuallyDisplayText, since that flushes out the content sink by
986 // calling SetText on a DOM node with aNotify set to true. See bug
987 // 289730.
988 nsWeakFrame weakThis(this);
989 PresContext()->Document()->
990 FlushPendingNotifications(Flush_ContentAndNotify);
991 if (!weakThis.IsAlive())
992 return;
994 // Redirect frame insertions during this method (see GetContentInsertionFrame())
995 // so that any reframing that the frame constructor forces upon us is inserted
996 // into the correct parent (mDisplayFrame). See bug 282607.
997 NS_PRECONDITION(!mInRedisplayText, "Nested RedisplayText");
998 mInRedisplayText = true;
999 mRedisplayTextEvent.Forget();
1001 ActuallyDisplayText(true);
1002 // XXXbz This should perhaps be eResize. Check.
1003 PresContext()->PresShell()->FrameNeedsReflow(mDisplayFrame,
1004 nsIPresShell::eStyleChange,
1005 NS_FRAME_IS_DIRTY);
1007 mInRedisplayText = false;
1010 void
1011 nsComboboxControlFrame::ActuallyDisplayText(bool aNotify)
1013 if (mDisplayedOptionText.IsEmpty()) {
1014 // Have to use a non-breaking space for line-height calculations
1015 // to be right
1016 static const char16_t space = 0xA0;
1017 mDisplayContent->SetText(&space, 1, aNotify);
1018 } else {
1019 mDisplayContent->SetText(mDisplayedOptionText, aNotify);
1023 int32_t
1024 nsComboboxControlFrame::GetIndexOfDisplayArea()
1026 return mDisplayedIndex;
1029 //----------------------------------------------------------------------
1030 // nsISelectControlFrame
1031 //----------------------------------------------------------------------
1032 NS_IMETHODIMP
1033 nsComboboxControlFrame::DoneAddingChildren(bool aIsDone)
1035 nsISelectControlFrame* listFrame = do_QueryFrame(mDropdownFrame);
1036 if (!listFrame)
1037 return NS_ERROR_FAILURE;
1039 return listFrame->DoneAddingChildren(aIsDone);
1042 NS_IMETHODIMP
1043 nsComboboxControlFrame::AddOption(int32_t aIndex)
1045 if (aIndex <= mDisplayedIndex) {
1046 ++mDisplayedIndex;
1049 nsListControlFrame* lcf = static_cast<nsListControlFrame*>(mDropdownFrame);
1050 return lcf->AddOption(aIndex);
1054 NS_IMETHODIMP
1055 nsComboboxControlFrame::RemoveOption(int32_t aIndex)
1057 nsWeakFrame weakThis(this);
1058 if (mListControlFrame->GetNumberOfOptions() > 0) {
1059 if (aIndex < mDisplayedIndex) {
1060 --mDisplayedIndex;
1061 } else if (aIndex == mDisplayedIndex) {
1062 mDisplayedIndex = 0; // IE6 compat
1063 RedisplayText(mDisplayedIndex);
1066 else {
1067 // If we removed the last option, we need to blank things out
1068 RedisplayText(-1);
1071 if (!weakThis.IsAlive())
1072 return NS_OK;
1074 nsListControlFrame* lcf = static_cast<nsListControlFrame*>(mDropdownFrame);
1075 return lcf->RemoveOption(aIndex);
1078 NS_IMETHODIMP
1079 nsComboboxControlFrame::OnSetSelectedIndex(int32_t aOldIndex, int32_t aNewIndex)
1081 nsAutoScriptBlocker scriptBlocker;
1082 RedisplayText(aNewIndex);
1083 NS_ASSERTION(mDropdownFrame, "No dropdown frame!");
1085 nsISelectControlFrame* listFrame = do_QueryFrame(mDropdownFrame);
1086 NS_ASSERTION(listFrame, "No list frame!");
1088 return listFrame->OnSetSelectedIndex(aOldIndex, aNewIndex);
1091 // End nsISelectControlFrame
1092 //----------------------------------------------------------------------
1094 NS_IMETHODIMP
1095 nsComboboxControlFrame::HandleEvent(nsPresContext* aPresContext,
1096 WidgetGUIEvent* aEvent,
1097 nsEventStatus* aEventStatus)
1099 NS_ENSURE_ARG_POINTER(aEventStatus);
1101 if (nsEventStatus_eConsumeNoDefault == *aEventStatus) {
1102 return NS_OK;
1105 nsEventStates eventStates = mContent->AsElement()->State();
1106 if (eventStates.HasState(NS_EVENT_STATE_DISABLED)) {
1107 return NS_OK;
1110 // If we have style that affects how we are selected, feed event down to
1111 // nsFrame::HandleEvent so that selection takes place when appropriate.
1112 const nsStyleUserInterface* uiStyle = StyleUserInterface();
1113 if (uiStyle->mUserInput == NS_STYLE_USER_INPUT_NONE || uiStyle->mUserInput == NS_STYLE_USER_INPUT_DISABLED)
1114 return nsBlockFrame::HandleEvent(aPresContext, aEvent, aEventStatus);
1116 return NS_OK;
1120 nsresult
1121 nsComboboxControlFrame::SetFormProperty(nsIAtom* aName, const nsAString& aValue)
1123 nsIFormControlFrame* fcFrame = do_QueryFrame(mDropdownFrame);
1124 if (!fcFrame) {
1125 return NS_NOINTERFACE;
1128 return fcFrame->SetFormProperty(aName, aValue);
1131 nsIFrame*
1132 nsComboboxControlFrame::GetContentInsertionFrame() {
1133 return mInRedisplayText ? mDisplayFrame : mDropdownFrame->GetContentInsertionFrame();
1136 nsresult
1137 nsComboboxControlFrame::CreateAnonymousContent(nsTArray<ContentInfo>& aElements)
1139 // The frames used to display the combo box and the button used to popup the dropdown list
1140 // are created through anonymous content. The dropdown list is not created through anonymous
1141 // content because it's frame is initialized specifically for the drop-down case and it is placed
1142 // a special list referenced through NS_COMBO_FRAME_POPUP_LIST_INDEX to keep separate from the
1143 // layout of the display and button.
1145 // Note: The value attribute of the display content is set when an item is selected in the dropdown list.
1146 // If the content specified below does not honor the value attribute than nothing will be displayed.
1148 // For now the content that is created corresponds to two input buttons. It would be better to create the
1149 // tag as something other than input, but then there isn't any way to create a button frame since it
1150 // isn't possible to set the display type in CSS2 to create a button frame.
1152 // create content used for display
1153 //nsIAtom* tag = NS_NewAtom("mozcombodisplay");
1155 // Add a child text content node for the label
1157 nsNodeInfoManager *nimgr = mContent->NodeInfo()->NodeInfoManager();
1159 mDisplayContent = new nsTextNode(nimgr);
1161 // set the value of the text node
1162 mDisplayedIndex = mListControlFrame->GetSelectedIndex();
1163 if (mDisplayedIndex != -1) {
1164 mListControlFrame->GetOptionText(mDisplayedIndex, mDisplayedOptionText);
1166 ActuallyDisplayText(false);
1168 if (!aElements.AppendElement(mDisplayContent))
1169 return NS_ERROR_OUT_OF_MEMORY;
1171 mButtonContent = mContent->OwnerDoc()->CreateHTMLElement(nsGkAtoms::button);
1172 if (!mButtonContent)
1173 return NS_ERROR_OUT_OF_MEMORY;
1175 // make someone to listen to the button. If its pressed by someone like Accessibility
1176 // then open or close the combo box.
1177 mButtonListener = new nsComboButtonListener(this);
1178 mButtonContent->AddEventListener(NS_LITERAL_STRING("click"), mButtonListener,
1179 false, false);
1181 mButtonContent->SetAttr(kNameSpaceID_None, nsGkAtoms::type,
1182 NS_LITERAL_STRING("button"), false);
1183 // Set tabindex="-1" so that the button is not tabbable
1184 mButtonContent->SetAttr(kNameSpaceID_None, nsGkAtoms::tabindex,
1185 NS_LITERAL_STRING("-1"), false);
1187 if (!aElements.AppendElement(mButtonContent))
1188 return NS_ERROR_OUT_OF_MEMORY;
1190 return NS_OK;
1193 void
1194 nsComboboxControlFrame::AppendAnonymousContentTo(nsBaseContentList& aElements,
1195 uint32_t aFilter)
1197 aElements.MaybeAppendElement(mDisplayContent);
1198 aElements.MaybeAppendElement(mButtonContent);
1201 // XXXbz this is a for-now hack. Now that display:inline-block works,
1202 // need to revisit this.
1203 class nsComboboxDisplayFrame : public nsBlockFrame {
1204 public:
1205 NS_DECL_FRAMEARENA_HELPERS
1207 nsComboboxDisplayFrame (nsStyleContext* aContext,
1208 nsComboboxControlFrame* aComboBox)
1209 : nsBlockFrame(aContext),
1210 mComboBox(aComboBox)
1213 // Need this so that line layout knows that this block's width
1214 // depends on the available width.
1215 virtual nsIAtom* GetType() const;
1217 virtual bool IsFrameOfType(uint32_t aFlags) const
1219 return nsBlockFrame::IsFrameOfType(aFlags &
1220 ~(nsIFrame::eReplacedContainsBlock));
1223 NS_IMETHOD Reflow(nsPresContext* aPresContext,
1224 nsHTMLReflowMetrics& aDesiredSize,
1225 const nsHTMLReflowState& aReflowState,
1226 nsReflowStatus& aStatus);
1228 virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder,
1229 const nsRect& aDirtyRect,
1230 const nsDisplayListSet& aLists) MOZ_OVERRIDE;
1232 protected:
1233 nsComboboxControlFrame* mComboBox;
1236 NS_IMPL_FRAMEARENA_HELPERS(nsComboboxDisplayFrame)
1238 nsIAtom*
1239 nsComboboxDisplayFrame::GetType() const
1241 return nsGkAtoms::comboboxDisplayFrame;
1244 NS_IMETHODIMP
1245 nsComboboxDisplayFrame::Reflow(nsPresContext* aPresContext,
1246 nsHTMLReflowMetrics& aDesiredSize,
1247 const nsHTMLReflowState& aReflowState,
1248 nsReflowStatus& aStatus)
1250 nsHTMLReflowState state(aReflowState);
1251 if (state.ComputedHeight() == NS_INTRINSICSIZE) {
1252 // Note that the only way we can have a computed height here is if the
1253 // combobox had a specified height. If it didn't, size based on what our
1254 // rows look like, for lack of anything better.
1255 state.SetComputedHeight(mComboBox->mListControlFrame->GetHeightOfARow());
1257 nscoord computedWidth = mComboBox->mDisplayWidth -
1258 state.ComputedPhysicalBorderPadding().LeftRight();
1259 if (computedWidth < 0) {
1260 computedWidth = 0;
1262 state.SetComputedWidth(computedWidth);
1264 return nsBlockFrame::Reflow(aPresContext, aDesiredSize, state, aStatus);
1267 void
1268 nsComboboxDisplayFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
1269 const nsRect& aDirtyRect,
1270 const nsDisplayListSet& aLists)
1272 nsDisplayListCollection set;
1273 nsBlockFrame::BuildDisplayList(aBuilder, aDirtyRect, set);
1275 // remove background items if parent frame is themed
1276 if (mComboBox->IsThemed()) {
1277 set.BorderBackground()->DeleteAll();
1280 set.MoveTo(aLists);
1283 nsIFrame*
1284 nsComboboxControlFrame::CreateFrameFor(nsIContent* aContent)
1286 NS_PRECONDITION(nullptr != aContent, "null ptr");
1288 NS_ASSERTION(mDisplayContent, "mDisplayContent can't be null!");
1290 if (mDisplayContent != aContent) {
1291 // We only handle the frames for mDisplayContent here
1292 return nullptr;
1295 // Get PresShell
1296 nsIPresShell *shell = PresContext()->PresShell();
1297 nsStyleSet *styleSet = shell->StyleSet();
1299 // create the style contexts for the anonymous block frame and text frame
1300 nsRefPtr<nsStyleContext> styleContext;
1301 styleContext = styleSet->
1302 ResolveAnonymousBoxStyle(nsCSSAnonBoxes::mozDisplayComboboxControlFrame,
1303 mStyleContext);
1305 nsRefPtr<nsStyleContext> textStyleContext;
1306 textStyleContext = styleSet->ResolveStyleForNonElement(mStyleContext);
1308 // Start by creating our anonymous block frame
1309 mDisplayFrame = new (shell) nsComboboxDisplayFrame(styleContext, this);
1310 mDisplayFrame->Init(mContent, this, nullptr);
1312 // Create a text frame and put it inside the block frame
1313 nsIFrame* textFrame = NS_NewTextFrame(shell, textStyleContext);
1315 // initialize the text frame
1316 textFrame->Init(aContent, mDisplayFrame, nullptr);
1317 mDisplayContent->SetPrimaryFrame(textFrame);
1319 nsFrameList textList(textFrame, textFrame);
1320 mDisplayFrame->SetInitialChildList(kPrincipalList, textList);
1321 return mDisplayFrame;
1324 void
1325 nsComboboxControlFrame::DestroyFrom(nsIFrame* aDestructRoot)
1327 // Revoke any pending RedisplayTextEvent
1328 mRedisplayTextEvent.Revoke();
1330 nsFormControlFrame::RegUnRegAccessKey(static_cast<nsIFrame*>(this), false);
1332 if (mDroppedDown) {
1333 MOZ_ASSERT(mDropdownFrame, "mDroppedDown without frame");
1334 nsView* view = mDropdownFrame->GetView();
1335 MOZ_ASSERT(view);
1336 nsIWidget* widget = view->GetWidget();
1337 if (widget) {
1338 widget->CaptureRollupEvents(this, false);
1342 // Cleanup frames in popup child list
1343 mPopupFrames.DestroyFramesFrom(aDestructRoot);
1344 nsContentUtils::DestroyAnonymousContent(&mDisplayContent);
1345 nsContentUtils::DestroyAnonymousContent(&mButtonContent);
1346 nsBlockFrame::DestroyFrom(aDestructRoot);
1349 const nsFrameList&
1350 nsComboboxControlFrame::GetChildList(ChildListID aListID) const
1352 if (kSelectPopupList == aListID) {
1353 return mPopupFrames;
1355 return nsBlockFrame::GetChildList(aListID);
1358 void
1359 nsComboboxControlFrame::GetChildLists(nsTArray<ChildList>* aLists) const
1361 nsBlockFrame::GetChildLists(aLists);
1362 mPopupFrames.AppendIfNonempty(aLists, kSelectPopupList);
1365 NS_IMETHODIMP
1366 nsComboboxControlFrame::SetInitialChildList(ChildListID aListID,
1367 nsFrameList& aChildList)
1369 nsresult rv = NS_OK;
1370 if (kSelectPopupList == aListID) {
1371 mPopupFrames.SetFrames(aChildList);
1372 } else {
1373 for (nsFrameList::Enumerator e(aChildList); !e.AtEnd(); e.Next()) {
1374 nsCOMPtr<nsIFormControl> formControl =
1375 do_QueryInterface(e.get()->GetContent());
1376 if (formControl && formControl->GetType() == NS_FORM_BUTTON_BUTTON) {
1377 mButtonFrame = e.get();
1378 break;
1381 NS_ASSERTION(mButtonFrame, "missing button frame in initial child list");
1382 rv = nsBlockFrame::SetInitialChildList(aListID, aChildList);
1384 return rv;
1387 //----------------------------------------------------------------------
1388 //nsIRollupListener
1389 //----------------------------------------------------------------------
1390 bool
1391 nsComboboxControlFrame::Rollup(uint32_t aCount, const nsIntPoint* pos, nsIContent** aLastRolledUp)
1393 if (!mDroppedDown)
1394 return false;
1396 nsWeakFrame weakFrame(this);
1397 mListControlFrame->AboutToRollup(); // might destroy us
1398 if (!weakFrame.IsAlive())
1399 return true;
1400 ShowDropDown(false); // might destroy us
1401 if (weakFrame.IsAlive()) {
1402 mListControlFrame->CaptureMouseEvents(false);
1405 return true;
1408 nsIWidget*
1409 nsComboboxControlFrame::GetRollupWidget()
1411 nsView* view = mDropdownFrame->GetView();
1412 MOZ_ASSERT(view);
1413 return view->GetWidget();
1416 void
1417 nsComboboxControlFrame::RollupFromList()
1419 if (ShowList(false))
1420 mListControlFrame->CaptureMouseEvents(false);
1423 int32_t
1424 nsComboboxControlFrame::UpdateRecentIndex(int32_t aIndex)
1426 int32_t index = mRecentSelectedIndex;
1427 if (mRecentSelectedIndex == NS_SKIP_NOTIFY_INDEX || aIndex == NS_SKIP_NOTIFY_INDEX)
1428 mRecentSelectedIndex = aIndex;
1429 return index;
1432 class nsDisplayComboboxFocus : public nsDisplayItem {
1433 public:
1434 nsDisplayComboboxFocus(nsDisplayListBuilder* aBuilder,
1435 nsComboboxControlFrame* aFrame)
1436 : nsDisplayItem(aBuilder, aFrame) {
1437 MOZ_COUNT_CTOR(nsDisplayComboboxFocus);
1439 #ifdef NS_BUILD_REFCNT_LOGGING
1440 virtual ~nsDisplayComboboxFocus() {
1441 MOZ_COUNT_DTOR(nsDisplayComboboxFocus);
1443 #endif
1445 virtual void Paint(nsDisplayListBuilder* aBuilder,
1446 nsRenderingContext* aCtx);
1447 NS_DISPLAY_DECL_NAME("ComboboxFocus", TYPE_COMBOBOX_FOCUS)
1450 void nsDisplayComboboxFocus::Paint(nsDisplayListBuilder* aBuilder,
1451 nsRenderingContext* aCtx)
1453 static_cast<nsComboboxControlFrame*>(mFrame)
1454 ->PaintFocus(*aCtx, ToReferenceFrame());
1457 void
1458 nsComboboxControlFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
1459 const nsRect& aDirtyRect,
1460 const nsDisplayListSet& aLists)
1462 #ifdef NOISY
1463 printf("%p paint at (%d, %d, %d, %d)\n", this,
1464 aDirtyRect.x, aDirtyRect.y, aDirtyRect.width, aDirtyRect.height);
1465 #endif
1467 if (aBuilder->IsForEventDelivery()) {
1468 // Don't allow children to receive events.
1469 // REVIEW: following old GetFrameForPoint
1470 DisplayBorderBackgroundOutline(aBuilder, aLists);
1471 } else {
1472 // REVIEW: Our in-flow child frames are inline-level so they will paint in our
1473 // content list, so we don't need to mess with layers.
1474 nsBlockFrame::BuildDisplayList(aBuilder, aDirtyRect, aLists);
1477 // draw a focus indicator only when focus rings should be drawn
1478 nsIDocument* doc = mContent->GetCurrentDoc();
1479 if (doc) {
1480 nsPIDOMWindow* window = doc->GetWindow();
1481 if (window && window->ShouldShowFocusRing()) {
1482 nsPresContext *presContext = PresContext();
1483 const nsStyleDisplay *disp = StyleDisplay();
1484 if ((!IsThemed(disp) ||
1485 !presContext->GetTheme()->ThemeDrawsFocusForWidget(disp->mAppearance)) &&
1486 mDisplayFrame && IsVisibleForPainting(aBuilder)) {
1487 aLists.Content()->AppendNewToTop(
1488 new (aBuilder) nsDisplayComboboxFocus(aBuilder, this));
1493 DisplaySelectionOverlay(aBuilder, aLists.Content());
1496 void nsComboboxControlFrame::PaintFocus(nsRenderingContext& aRenderingContext,
1497 nsPoint aPt)
1499 /* Do we need to do anything? */
1500 nsEventStates eventStates = mContent->AsElement()->State();
1501 if (eventStates.HasState(NS_EVENT_STATE_DISABLED) || sFocused != this)
1502 return;
1504 aRenderingContext.PushState();
1505 nsRect clipRect = mDisplayFrame->GetRect() + aPt;
1506 aRenderingContext.IntersectClip(clipRect);
1508 // REVIEW: Why does the old code paint mDisplayFrame again? We've
1509 // already painted it in the children above. So clipping it here won't do
1510 // us much good.
1512 /////////////////////
1513 // draw focus
1515 aRenderingContext.SetLineStyle(nsLineStyle_kDotted);
1516 aRenderingContext.SetColor(StyleColor()->mColor);
1518 //aRenderingContext.DrawRect(clipRect);
1520 nscoord onePixel = nsPresContext::CSSPixelsToAppUnits(1);
1521 clipRect.width -= onePixel;
1522 clipRect.height -= onePixel;
1523 aRenderingContext.DrawLine(clipRect.TopLeft(), clipRect.TopRight());
1524 aRenderingContext.DrawLine(clipRect.TopRight(), clipRect.BottomRight());
1525 aRenderingContext.DrawLine(clipRect.BottomRight(), clipRect.BottomLeft());
1526 aRenderingContext.DrawLine(clipRect.BottomLeft(), clipRect.TopLeft());
1528 aRenderingContext.PopState();
1531 //---------------------------------------------------------
1532 // gets the content (an option) by index and then set it as
1533 // being selected or not selected
1534 //---------------------------------------------------------
1535 NS_IMETHODIMP
1536 nsComboboxControlFrame::OnOptionSelected(int32_t aIndex, bool aSelected)
1538 if (mDroppedDown) {
1539 nsISelectControlFrame *selectFrame = do_QueryFrame(mListControlFrame);
1540 if (selectFrame) {
1541 selectFrame->OnOptionSelected(aIndex, aSelected);
1543 } else {
1544 if (aSelected) {
1545 nsAutoScriptBlocker blocker;
1546 RedisplayText(aIndex);
1547 } else {
1548 nsWeakFrame weakFrame(this);
1549 RedisplaySelectedText();
1550 if (weakFrame.IsAlive()) {
1551 FireValueChangeEvent(); // Fire after old option is unselected
1556 return NS_OK;
1559 void nsComboboxControlFrame::FireValueChangeEvent()
1561 // Fire ValueChange event to indicate data value of combo box has changed
1562 nsContentUtils::AddScriptRunner(
1563 new nsAsyncDOMEvent(mContent, NS_LITERAL_STRING("ValueChange"), true,
1564 false));
1567 void
1568 nsComboboxControlFrame::OnContentReset()
1570 if (mListControlFrame) {
1571 mListControlFrame->OnContentReset();
1576 //--------------------------------------------------------
1577 // nsIStatefulFrame
1578 //--------------------------------------------------------
1579 NS_IMETHODIMP
1580 nsComboboxControlFrame::SaveState(nsPresState** aState)
1582 if (!mListControlFrame)
1583 return NS_ERROR_FAILURE;
1585 nsIStatefulFrame* stateful = do_QueryFrame(mListControlFrame);
1586 return stateful->SaveState(aState);
1589 NS_IMETHODIMP
1590 nsComboboxControlFrame::RestoreState(nsPresState* aState)
1592 if (!mListControlFrame)
1593 return NS_ERROR_FAILURE;
1595 nsIStatefulFrame* stateful = do_QueryFrame(mListControlFrame);
1596 NS_ASSERTION(stateful, "Must implement nsIStatefulFrame");
1597 return stateful->RestoreState(aState);
1602 // Camino uses a native widget for the combobox
1603 // popup, which affects drawing and event
1604 // handling here and in nsListControlFrame.
1606 // Also, Fennec use a custom combobox built-in widget
1609 /* static */
1610 bool
1611 nsComboboxControlFrame::ToolkitHasNativePopup()
1613 #ifdef MOZ_USE_NATIVE_POPUP_WINDOWS
1614 return true;
1615 #else
1616 return false;
1617 #endif /* MOZ_USE_NATIVE_POPUP_WINDOWS */