Bug 767636 - Expose plugin fallback type to extensions. r=josh
[gecko.git] / layout / forms / nsComboboxControlFrame.cpp
blob68895d9f597b52176dc480508382bf36b08e3be3
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 "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"
25 #include "nsIView.h"
26 #include "nsIViewManager.h"
27 #include "nsEventDispatcher.h"
28 #include "nsEventListenerManager.h"
29 #include "nsIDOMNode.h"
30 #include "nsISelectControlFrame.h"
31 #include "nsXPCOM.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"
42 #ifdef ACCESSIBILITY
43 #include "nsAccessibilityService.h"
44 #endif
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"
53 #include "nsITheme.h"
54 #include "nsThemeConstants.h"
55 #include "nsAsyncDOMEvent.h"
56 #include "nsRenderingContext.h"
57 #include "mozilla/Preferences.h"
59 using namespace mozilla;
61 NS_IMETHODIMP
62 nsComboboxControlFrame::RedisplayTextEvent::Run()
64 if (mControlFrame)
65 mControlFrame->HandleRedisplayTextEvent();
66 return NS_OK;
69 class nsPresState;
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
80 // actually selected.
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 PRInt32 kSizeNotSet = -1;
89 /**
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
96 public:
97 NS_DECL_ISUPPORTS
99 NS_IMETHOD HandleEvent(nsIDOMEvent*)
101 mComboBox->ShowDropDown(!mComboBox->IsDroppedDown());
102 return NS_OK;
105 nsComboButtonListener(nsComboboxControlFrame* aCombobox)
107 mComboBox = aCombobox;
110 virtual ~nsComboButtonListener() {}
112 nsComboboxControlFrame* mComboBox;
115 NS_IMPL_ISUPPORTS1(nsComboButtonListener,
116 nsIDOMEventListener)
118 // static class data member for Bug 32920
119 nsComboboxControlFrame* nsComboboxControlFrame::sFocused = nullptr;
121 nsIFrame*
122 NS_NewComboboxControlFrame(nsIPresShell* aPresShell, nsStyleContext* aContext, PRUint32 aStateFlags)
124 nsComboboxControlFrame* it = new (aPresShell) nsComboboxControlFrame(aContext);
126 if (it) {
127 // set the state flags (if any are provided)
128 it->AddStateBits(aStateFlags);
131 return it;
134 NS_IMPL_FRAMEARENA_HELPERS(nsComboboxControlFrame)
136 namespace {
138 class DestroyWidgetRunnable : public nsRunnable {
139 public:
140 NS_DECL_NSIRUNNABLE
142 explicit DestroyWidgetRunnable(nsIContent* aCombobox) :
143 mCombobox(aCombobox),
144 mWidget(GetWidget())
148 private:
149 nsIWidget* GetWidget(nsIView** aOutView = nullptr) const;
151 private:
152 nsCOMPtr<nsIContent> mCombobox;
153 nsIWidget* mWidget;
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();
165 return NS_OK;
168 nsIWidget* DestroyWidgetRunnable::GetWidget(nsIView** aOutView) const
170 nsIFrame* primaryFrame = mCombobox->GetPrimaryFrame();
171 nsIComboboxControlFrame* comboboxFrame = do_QueryFrame(primaryFrame);
172 if (comboboxFrame) {
173 nsIFrame* dropdown = comboboxFrame->GetDropDown();
174 if (dropdown) {
175 nsIView* view = dropdown->GetView();
176 NS_ASSERTION(view, "nsComboboxControlFrame view is null");
177 if (aOutView) {
178 *aOutView = view;
180 if (view) {
181 return view->GetWidget();
185 return nullptr;
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 PRInt32 gTotalReqs = 0;;
198 static PRInt32 gTotalReflows = 0;;
199 static PRInt32 gReflowControlCntRQ[MAX_REFLOW_CNT];
200 static PRInt32 gReflowControlCnt[MAX_REFLOW_CNT];
201 static PRInt32 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) { \
224 gReflowInx++; \
225 mReflowId = gReflowInx; \
226 gReflowControlCnt[mReflowId] = 0; \
227 gReflowControlCntRQ[mReflowId] = 0; \
228 } else { \
229 mReflowId = -1; \
232 // reflow messages
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)
251 #endif
253 //------------------------------------------
254 // This is for being VERY noisy
255 //------------------------------------------
256 #ifdef DO_VERY_NOISY
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))
261 #else
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)
266 #endif
268 //------------------------------------------
269 // Displays value in pixels or twips
270 //------------------------------------------
271 #ifdef DO_PIXELS
272 #define PX(__v) __v / 15
273 #else
274 #define PX(__v) __v
275 #endif
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)
287 , mDisplayWidth(0)
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)
313 #ifdef ACCESSIBILITY
314 already_AddRefed<Accessible>
315 nsComboboxControlFrame::CreateAccessible()
317 nsAccessibilityService* accService = nsIPresShell::AccService();
318 if (accService) {
319 return accService->CreateHTMLComboboxAccessible(mContent,
320 PresContext()->PresShell());
323 return nullptr;
325 #endif
327 void
328 nsComboboxControlFrame::SetFocus(bool aOn, bool aRepaint)
330 nsWeakFrame weakFrame(this);
331 if (aOn) {
332 nsListControlFrame::ComboboxFocusSet();
333 sFocused = this;
334 if (mDelayedShowDropDown) {
335 ShowDropDown(true); // might destroy us
336 if (!weakFrame.IsAlive()) {
337 return;
340 } else {
341 sFocused = nullptr;
342 mDelayedShowDropDown = false;
343 if (mDroppedDown) {
344 mListControlFrame->ComboboxFinish(mDisplayedIndex); // might destroy us
345 if (!weakFrame.IsAlive()) {
346 return;
349 // May delete |this|.
350 mListControlFrame->FireOnChange();
353 if (!weakFrame.IsAlive()) {
354 return;
357 // This is needed on a temporary basis. It causes the focus
358 // rect to be drawn. This is much faster than ReResolvingStyle
359 // Bug 32920
360 Invalidate(nsRect(0,0,mRect.width,mRect.height));
363 void
364 nsComboboxControlFrame::ShowPopup(bool aShowPopup)
366 nsIView* view = mDropdownFrame->GetView();
367 nsIViewManager* viewManager = view->GetViewManager();
369 if (aShowPopup) {
370 nsRect rect = mDropdownFrame->GetRect();
371 rect.x = rect.y = 0;
372 viewManager->ResizeView(view, rect);
373 viewManager->SetViewVisibility(view, nsViewVisibility_kShow);
374 } else {
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();
387 if (shell)
388 shell->HandleDOMEventWithTarget(mContent, &event, &status);
391 bool
392 nsComboboxControlFrame::ShowList(bool aShowList)
394 nsCOMPtr<nsIPresShell> shell = PresContext()->GetPresShell();
396 nsWeakFrame weakFrame(this);
398 if (aShowList) {
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()) {
414 return false;
417 mDroppedDown = aShowList;
418 if (mDroppedDown) {
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()) {
428 return false;
431 nsIFrame* listFrame = do_QueryFrame(mListControlFrame);
432 if (listFrame) {
433 nsIView* view = listFrame->GetView();
434 NS_ASSERTION(view, "nsComboboxControlFrame view is null");
435 if (view) {
436 nsIWidget* widget = view->GetWidget();
437 if (widget) {
438 widget->CaptureRollupEvents(this, mDroppedDown, mDroppedDown);
440 if (!aShowList) {
441 nsCOMPtr<nsIRunnable> widgetDestroyer =
442 new DestroyWidgetRunnable(GetContent());
443 NS_DispatchToMainThread(widgetDestroyer);
449 return weakFrame.IsAlive();
452 class nsResizeDropdownAtFinalPosition
453 : public nsIReflowCallback, public nsRunnable
455 public:
456 nsResizeDropdownAtFinalPosition(nsComboboxControlFrame* aFrame)
457 : mFrame(aFrame)
459 MOZ_COUNT_CTOR(nsResizeDropdownAtFinalPosition);
461 ~nsResizeDropdownAtFinalPosition()
463 MOZ_COUNT_DTOR(nsResizeDropdownAtFinalPosition);
466 virtual bool ReflowFinished()
468 Run();
469 NS_RELEASE_THIS();
470 return false;
473 virtual void ReflowCallbackCanceled()
475 NS_RELEASE_THIS();
478 NS_IMETHODIMP Run()
480 if (mFrame.IsAlive()) {
481 static_cast<nsComboboxControlFrame*>(mFrame.GetFrame())->
482 AbsolutelyPositionDropDown();
484 return NS_OK;
487 nsWeakFrame mFrame;
490 nsresult
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)) {
499 return NS_OK;
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
504 // for now.
505 nsSize availSize(aReflowState.availableWidth, NS_UNCONSTRAINEDSIZE);
506 nsHTMLReflowState kidReflowState(aPresContext, aReflowState, mDropdownFrame,
507 availSize);
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(),
516 forcedWidth));
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
528 // dropped down
529 PRInt32 flags = NS_FRAME_NO_MOVE_FRAME | NS_FRAME_NO_VISIBILITY | NS_FRAME_NO_SIZE_VIEW;
530 if (mDroppedDown) {
531 flags = 0;
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,
538 ignoredStatus);
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);
543 return rv;
546 nsPoint
547 nsComboboxControlFrame::GetCSSTransformTranslation()
549 nsIFrame* frame = this;
550 bool is3DTransform = false;
551 gfxMatrix transform;
552 while (frame) {
553 nsIFrame* parent;
554 gfx3DMatrix ctm = frame->GetTransformMatrix(nullptr, &parent);
555 gfxMatrix matrix;
556 if (ctm.Is2D(&matrix)) {
557 transform = transform * matrix;
558 } else {
559 is3DTransform = true;
560 break;
562 frame = parent;
564 nsPoint translation;
565 if (!is3DTransform && !transform.HasNonTranslation()) {
566 nsPresContext* pc = PresContext();
567 gfxPoint pixelTranslation = transform.GetTranslation();
568 PRInt32 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();
574 if (rootPC) {
575 translation -= GetOffsetToCrossDoc(rootPC->PresShell()->GetRootFrame());
576 } else {
577 translation.x = translation.y = 0;
580 return translation;
583 class nsAsyncRollup : public nsRunnable
585 public:
586 nsAsyncRollup(nsComboboxControlFrame* aFrame) : mFrame(aFrame) {}
587 NS_IMETHODIMP Run()
589 if (mFrame.IsAlive()) {
590 static_cast<nsComboboxControlFrame*>(mFrame.GetFrame())
591 ->RollupFromList();
593 return NS_OK;
595 nsWeakFrame mFrame;
598 class nsAsyncResize : public nsRunnable
600 public:
601 nsAsyncResize(nsComboboxControlFrame* aFrame) : mFrame(aFrame) {}
602 NS_IMETHODIMP Run()
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,
611 NS_FRAME_IS_DIRTY);
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);
622 return NS_OK;
624 nsWeakFrame mFrame;
627 void
628 nsComboboxControlFrame::GetAvailableDropdownSpace(nscoord* aAbove,
629 nscoord* aBelow,
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
639 // offscreen.
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();
645 *aAbove = 0;
646 *aBelow = 0;
648 nsRect thisScreenRect = GetScreenRectInAppUnits();
649 nsRect screen = nsFormControlFrame::GetUsableScreenRect(PresContext());
650 nscoord dropdownY = thisScreenRect.YMost() + aTranslation->y;
652 nscoord minY;
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.
658 return;
660 } else {
661 minY = screen.y;
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) {
673 above -= rowHeight;
677 *aBelow = below;
678 *aAbove = above;
681 nsComboboxControlFrame::DropDownPositionState
682 nsComboboxControlFrame::AbsolutelyPositionDropDown()
684 nsPoint translation;
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;
735 void
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) {
762 printf("UC");
763 } else {
764 printf("%d", PX(aSize));
767 #endif
769 //-------------------------------------------------------------------
770 //-- Main Reflow for the Combobox
771 //-------------------------------------------------------------------
773 nscoord
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");
783 scrollbarWidth =
784 scrollable->GetDesiredScrollbarSizes(presContext, aRenderingContext).LeftRight();
787 nscoord displayWidth = 0;
788 if (NS_LIKELY(mDisplayFrame)) {
789 displayWidth = nsLayoutUtils::IntrinsicForContainer(aRenderingContext,
790 mDisplayFrame,
791 aType);
794 if (mDropdownFrame) {
795 nscoord dropdownContentWidth;
796 if (aType == nsLayoutUtils::MIN_WIDTH) {
797 dropdownContentWidth = mDropdownFrame->GetMinWidth(aRenderingContext);
798 } else {
799 NS_ASSERTION(aType == nsLayoutUtils::PREF_WIDTH, "Unexpected type");
800 dropdownContentWidth = mDropdownFrame->GetPrefWidth(aRenderingContext);
802 dropdownContentWidth = NSCoordSaturatingSubtract(dropdownContentWidth,
803 scrollbarWidth,
804 nscoord_MAX);
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;
813 return displayWidth;
817 nscoord
818 nsComboboxControlFrame::GetMinWidth(nsRenderingContext *aRenderingContext)
820 nscoord minWidth;
821 DISPLAY_MIN_WIDTH(this, minWidth);
822 minWidth = GetIntrinsicWidth(aRenderingContext, nsLayoutUtils::MIN_WIDTH);
823 return minWidth;
826 nscoord
827 nsComboboxControlFrame::GetPrefWidth(nsRenderingContext *aRenderingContext)
829 nscoord prefWidth;
830 DISPLAY_PREF_WIDTH(this, prefWidth);
831 prefWidth = GetIntrinsicWidth(aRenderingContext, nsLayoutUtils::PREF_WIDTH);
832 return prefWidth;
835 NS_IMETHODIMP
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
845 // button to 0.
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 PRInt32 selectedIndex;
859 nsAutoString selectedOptionText;
860 if (!mDroppedDown) {
861 selectedIndex = mListControlFrame->GetSelectedIndex();
863 else {
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.
882 resize.forget();
885 // Get the width of the vertical scrollbar. That will be the width of the
886 // dropdown button.
887 nscoord buttonWidth;
888 const nsStyleDisplay *disp = GetStyleDisplay();
889 if (IsThemed(disp) && !aPresContext->GetTheme()->ThemeNeedsComboboxDropmarker()) {
890 buttonWidth = 0;
892 else {
893 nsIScrollableFrame* scrollable = do_QueryFrame(mListControlFrame);
894 NS_ASSERTION(scrollable, "List must be a scrollable frame");
895 buttonWidth =
896 scrollable->GetDesiredScrollbarSizes(PresContext(),
897 aReflowState.rendContext).LeftRight();
898 if (buttonWidth > aReflowState.ComputedWidth()) {
899 buttonWidth = 0;
903 mDisplayWidth = aReflowState.ComputedWidth() - buttonWidth;
905 nsresult rv = nsBlockFrame::Reflow(aPresContext, aDesiredSize, aReflowState,
906 aStatus);
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;
921 #ifdef DEBUG
922 else {
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 &&
930 buttonHeight ==
931 mButtonFrame->GetUsedBorderAndPadding().TopBottom()) ||
932 (aReflowState.ComputedHeight() < displayHeight &&
933 displayHeight ==
934 mDisplayFrame->GetUsedBorderAndPadding().TopBottom()),
935 "Different heights?");
937 #endif
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);
946 return rv;
949 //--------------------------------------------------------------
951 nsIAtom*
952 nsComboboxControlFrame::GetType() const
954 return nsGkAtoms::comboboxControlFrame;
957 #ifdef DEBUG
958 NS_IMETHODIMP
959 nsComboboxControlFrame::GetFrameName(nsAString& aResult) const
961 return MakeFrameName(NS_LITERAL_STRING("ComboboxControl"), aResult);
963 #endif
966 //----------------------------------------------------------------------
967 // nsIComboboxControlFrame
968 //----------------------------------------------------------------------
969 void
970 nsComboboxControlFrame::ShowDropDown(bool aDoDropDown)
972 mDelayedShowDropDown = false;
973 nsEventStates eventStates = mContent->AsElement()->State();
974 if (aDoDropDown && eventStates.HasState(NS_EVENT_STATE_DISABLED)) {
975 return;
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;
987 } else {
988 // Delay until we get focus, see SetFocus().
989 mDelayedShowDropDown = true;
991 } else if (mDroppedDown && !aDoDropDown) {
992 ShowList(aDoDropDown); // might destroy us
996 void
997 nsComboboxControlFrame::SetDropDown(nsIFrame* aDropDownFrame)
999 mDropdownFrame = aDropDownFrame;
1000 mListControlFrame = do_QueryFrame(mDropdownFrame);
1003 nsIFrame*
1004 nsComboboxControlFrame::GetDropDown()
1006 return mDropdownFrame;
1009 ///////////////////////////////////////////////////////////////
1011 NS_IMETHODIMP
1012 nsComboboxControlFrame::RedisplaySelectedText()
1014 nsAutoScriptBlocker scriptBlocker;
1015 return RedisplayText(mListControlFrame->GetSelectedIndex());
1018 nsresult
1019 nsComboboxControlFrame::RedisplayText(PRInt32 aIndex)
1021 // Get the text to display
1022 if (aIndex != -1) {
1023 mListControlFrame->GetOptionText(aIndex, mDisplayedOptionText);
1024 } else {
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 "
1045 "ourselves!");
1047 nsRefPtr<RedisplayTextEvent> event = new RedisplayTextEvent(this);
1048 mRedisplayTextEvent = event;
1049 if (!nsContentUtils::AddScriptRunner(event))
1050 mRedisplayTextEvent.Forget();
1052 return rv;
1055 void
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
1063 // 289730.
1064 nsWeakFrame weakThis(this);
1065 PresContext()->Document()->
1066 FlushPendingNotifications(Flush_ContentAndNotify);
1067 if (!weakThis.IsAlive())
1068 return;
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,
1081 NS_FRAME_IS_DIRTY);
1083 mInRedisplayText = false;
1086 void
1087 nsComboboxControlFrame::ActuallyDisplayText(bool aNotify)
1089 if (mDisplayedOptionText.IsEmpty()) {
1090 // Have to use a non-breaking space for line-height calculations
1091 // to be right
1092 static const PRUnichar space = 0xA0;
1093 mDisplayContent->SetText(&space, 1, aNotify);
1094 } else {
1095 mDisplayContent->SetText(mDisplayedOptionText, aNotify);
1099 PRInt32
1100 nsComboboxControlFrame::GetIndexOfDisplayArea()
1102 return mDisplayedIndex;
1105 //----------------------------------------------------------------------
1106 // nsISelectControlFrame
1107 //----------------------------------------------------------------------
1108 NS_IMETHODIMP
1109 nsComboboxControlFrame::DoneAddingChildren(bool aIsDone)
1111 nsISelectControlFrame* listFrame = do_QueryFrame(mDropdownFrame);
1112 if (!listFrame)
1113 return NS_ERROR_FAILURE;
1115 return listFrame->DoneAddingChildren(aIsDone);
1118 NS_IMETHODIMP
1119 nsComboboxControlFrame::AddOption(PRInt32 aIndex)
1121 if (aIndex <= mDisplayedIndex) {
1122 ++mDisplayedIndex;
1125 nsListControlFrame* lcf = static_cast<nsListControlFrame*>(mDropdownFrame);
1126 return lcf->AddOption(aIndex);
1130 NS_IMETHODIMP
1131 nsComboboxControlFrame::RemoveOption(PRInt32 aIndex)
1133 nsWeakFrame weakThis(this);
1134 if (mListControlFrame->GetNumberOfOptions() > 0) {
1135 if (aIndex < mDisplayedIndex) {
1136 --mDisplayedIndex;
1137 } else if (aIndex == mDisplayedIndex) {
1138 mDisplayedIndex = 0; // IE6 compat
1139 RedisplayText(mDisplayedIndex);
1142 else {
1143 // If we removed the last option, we need to blank things out
1144 RedisplayText(-1);
1147 if (!weakThis.IsAlive())
1148 return NS_OK;
1150 nsListControlFrame* lcf = static_cast<nsListControlFrame*>(mDropdownFrame);
1151 return lcf->RemoveOption(aIndex);
1154 NS_IMETHODIMP
1155 nsComboboxControlFrame::OnSetSelectedIndex(PRInt32 aOldIndex, PRInt32 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 //----------------------------------------------------------------------
1170 NS_IMETHODIMP
1171 nsComboboxControlFrame::HandleEvent(nsPresContext* aPresContext,
1172 nsGUIEvent* aEvent,
1173 nsEventStatus* aEventStatus)
1175 NS_ENSURE_ARG_POINTER(aEventStatus);
1177 if (nsEventStatus_eConsumeNoDefault == *aEventStatus) {
1178 return NS_OK;
1181 nsEventStates eventStates = mContent->AsElement()->State();
1182 if (eventStates.HasState(NS_EVENT_STATE_DISABLED)) {
1183 return NS_OK;
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);
1192 return NS_OK;
1196 nsresult
1197 nsComboboxControlFrame::SetFormProperty(nsIAtom* aName, const nsAString& aValue)
1199 nsIFormControlFrame* fcFrame = do_QueryFrame(mDropdownFrame);
1200 if (!fcFrame) {
1201 return NS_NOINTERFACE;
1204 return fcFrame->SetFormProperty(aName, aValue);
1207 nsresult
1208 nsComboboxControlFrame::GetFormProperty(nsIAtom* aName, nsAString& aValue) const
1210 nsIFormControlFrame* fcFrame = do_QueryFrame(mDropdownFrame);
1211 if (!fcFrame) {
1212 return NS_ERROR_FAILURE;
1215 return fcFrame->GetFormProperty(aName, aValue);
1218 nsIFrame*
1219 nsComboboxControlFrame::GetContentInsertionFrame() {
1220 return mInRedisplayText ? mDisplayFrame : mDropdownFrame->GetContentInsertionFrame();
1223 nsresult
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,
1274 false, false);
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;
1285 return NS_OK;
1288 void
1289 nsComboboxControlFrame::AppendAnonymousContentTo(nsBaseContentList& aElements,
1290 PRUint32 aFilter)
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 {
1299 public:
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(PRUint32 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);
1327 protected:
1328 nsComboboxControlFrame* mComboBox;
1331 NS_IMPL_FRAMEARENA_HELPERS(nsComboboxDisplayFrame)
1333 nsIAtom*
1334 nsComboboxDisplayFrame::GetType() const
1336 return nsGkAtoms::comboboxDisplayFrame;
1339 NS_IMETHODIMP
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) {
1355 computedWidth = 0;
1357 state.SetComputedWidth(computedWidth);
1359 return nsBlockFrame::Reflow(aPresContext, aDesiredSize, state, aStatus);
1362 NS_IMETHODIMP
1363 nsComboboxDisplayFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
1364 const nsRect& aDirtyRect,
1365 const nsDisplayListSet& aLists)
1367 nsDisplayListCollection set;
1368 nsresult rv = nsBlockFrame::BuildDisplayList(aBuilder, aDirtyRect, set);
1369 if (NS_FAILED(rv))
1370 return rv;
1372 // remove background items if parent frame is themed
1373 if (mComboBox->IsThemed()) {
1374 set.BorderBackground()->DeleteAll();
1377 set.MoveTo(aLists);
1379 return NS_OK;
1382 nsIFrame*
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
1391 return nullptr;
1394 // Get PresShell
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,
1402 mStyleContext);
1403 if (NS_UNLIKELY(!styleContext)) {
1404 return nullptr;
1407 nsRefPtr<nsStyleContext> textStyleContext;
1408 textStyleContext = styleSet->ResolveStyleForNonElement(mStyleContext);
1409 if (NS_UNLIKELY(!textStyleContext)) {
1410 return nullptr;
1413 // Start by by creating our anonymous block frame
1414 mDisplayFrame = new (shell) nsComboboxDisplayFrame(styleContext, this);
1415 if (NS_UNLIKELY(!mDisplayFrame)) {
1416 return nullptr;
1419 nsresult rv = mDisplayFrame->Init(mContent, this, nullptr);
1420 if (NS_FAILED(rv)) {
1421 mDisplayFrame->Destroy();
1422 mDisplayFrame = nullptr;
1423 return 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)) {
1429 return nullptr;
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;
1439 return nullptr;
1441 mDisplayContent->SetPrimaryFrame(textFrame);
1443 nsFrameList textList(textFrame, textFrame);
1444 mDisplayFrame->SetInitialChildList(kPrincipalList, textList);
1445 return mDisplayFrame;
1448 void
1449 nsComboboxControlFrame::DestroyFrom(nsIFrame* aDestructRoot)
1451 // Revoke any pending RedisplayTextEvent
1452 mRedisplayTextEvent.Revoke();
1454 nsFormControlFrame::RegUnRegAccessKey(static_cast<nsIFrame*>(this), false);
1456 if (mDroppedDown) {
1457 // Get parent view
1458 nsIFrame * listFrame = do_QueryFrame(mListControlFrame);
1459 if (listFrame) {
1460 nsIView* view = listFrame->GetView();
1461 NS_ASSERTION(view, "nsComboboxControlFrame view is null");
1462 if (view) {
1463 nsIWidget* widget = view->GetWidget();
1464 if (widget)
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);
1477 const nsFrameList&
1478 nsComboboxControlFrame::GetChildList(ChildListID aListID) const
1480 if (kSelectPopupList == aListID) {
1481 return mPopupFrames;
1483 return nsBlockFrame::GetChildList(aListID);
1486 void
1487 nsComboboxControlFrame::GetChildLists(nsTArray<ChildList>* aLists) const
1489 nsBlockFrame::GetChildLists(aLists);
1490 mPopupFrames.AppendIfNonempty(aLists, kSelectPopupList);
1493 NS_IMETHODIMP
1494 nsComboboxControlFrame::SetInitialChildList(ChildListID aListID,
1495 nsFrameList& aChildList)
1497 nsresult rv = NS_OK;
1498 if (kSelectPopupList == aListID) {
1499 mPopupFrames.SetFrames(aChildList);
1500 } else {
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();
1506 break;
1509 NS_ASSERTION(mButtonFrame, "missing button frame in initial child list");
1510 rv = nsBlockFrame::SetInitialChildList(aListID, aChildList);
1512 return rv;
1515 //----------------------------------------------------------------------
1516 //nsIRollupListener
1517 //----------------------------------------------------------------------
1518 nsIContent*
1519 nsComboboxControlFrame::Rollup(PRUint32 aCount, bool aGetLastRolledUp)
1521 if (mDroppedDown) {
1522 nsWeakFrame weakFrame(this);
1523 mListControlFrame->AboutToRollup(); // might destroy us
1524 if (!weakFrame.IsAlive())
1525 return nullptr;
1526 ShowDropDown(false); // might destroy us
1527 if (!weakFrame.IsAlive())
1528 return nullptr;
1529 mListControlFrame->CaptureMouseEvents(false);
1532 return nullptr;
1535 void
1536 nsComboboxControlFrame::RollupFromList()
1538 if (ShowList(false))
1539 mListControlFrame->CaptureMouseEvents(false);
1542 PRInt32
1543 nsComboboxControlFrame::UpdateRecentIndex(PRInt32 aIndex)
1545 PRInt32 index = mRecentSelectedIndex;
1546 if (mRecentSelectedIndex == NS_SKIP_NOTIFY_INDEX || aIndex == NS_SKIP_NOTIFY_INDEX)
1547 mRecentSelectedIndex = aIndex;
1548 return index;
1551 class nsDisplayComboboxFocus : public nsDisplayItem {
1552 public:
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);
1562 #endif
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());
1576 NS_IMETHODIMP
1577 nsComboboxControlFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
1578 const nsRect& aDirtyRect,
1579 const nsDisplayListSet& aLists)
1581 #ifdef NOISY
1582 printf("%p paint at (%d, %d, %d, %d)\n", this,
1583 aDirtyRect.x, aDirtyRect.y, aDirtyRect.width, aDirtyRect.height);
1584 #endif
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);
1591 } else {
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();
1600 if (doc) {
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,
1619 nsPoint aPt)
1621 /* Do we need to do anything? */
1622 nsEventStates eventStates = mContent->AsElement()->State();
1623 if (eventStates.HasState(NS_EVENT_STATE_DISABLED) || sFocused != this)
1624 return;
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
1632 // us much good.
1634 /////////////////////
1635 // draw focus
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 //---------------------------------------------------------
1657 NS_IMETHODIMP
1658 nsComboboxControlFrame::OnOptionSelected(PRInt32 aIndex, bool aSelected)
1660 if (mDroppedDown) {
1661 nsISelectControlFrame *selectFrame = do_QueryFrame(mListControlFrame);
1662 if (selectFrame) {
1663 selectFrame->OnOptionSelected(aIndex, aSelected);
1665 } else {
1666 if (aSelected) {
1667 nsAutoScriptBlocker blocker;
1668 RedisplayText(aIndex);
1669 } else {
1670 nsWeakFrame weakFrame(this);
1671 RedisplaySelectedText();
1672 if (weakFrame.IsAlive()) {
1673 FireValueChangeEvent(); // Fire after old option is unselected
1678 return NS_OK;
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,
1686 false));
1689 void
1690 nsComboboxControlFrame::OnContentReset()
1692 if (mListControlFrame) {
1693 mListControlFrame->OnContentReset();
1698 //--------------------------------------------------------
1699 // nsIStatefulFrame
1700 //--------------------------------------------------------
1701 NS_IMETHODIMP
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);
1712 NS_IMETHODIMP
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
1732 /* static */
1733 bool
1734 nsComboboxControlFrame::ToolkitHasNativePopup()
1736 #ifdef MOZ_USE_NATIVE_POPUP_WINDOWS
1737 return true;
1738 #else
1739 return false;
1740 #endif /* MOZ_USE_NATIVE_POPUP_WINDOWS */