Bumping manifests a=b2g-bump
[gecko.git] / layout / forms / nsComboboxControlFrame.cpp
blob175013a135b992a785f836b463129e8ac3787880
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "nsComboboxControlFrame.h"
8 #include "gfxUtils.h"
9 #include "mozilla/gfx/2D.h"
10 #include "mozilla/gfx/PathHelpers.h"
11 #include "nsCOMPtr.h"
12 #include "nsFocusManager.h"
13 #include "nsFormControlFrame.h"
14 #include "nsGkAtoms.h"
15 #include "nsCSSAnonBoxes.h"
16 #include "nsHTMLParts.h"
17 #include "nsIFormControl.h"
18 #include "nsNameSpaceManager.h"
19 #include "nsIListControlFrame.h"
20 #include "nsPIDOMWindow.h"
21 #include "nsIPresShell.h"
22 #include "nsContentList.h"
23 #include "nsView.h"
24 #include "nsViewManager.h"
25 #include "nsIDOMEventListener.h"
26 #include "nsIDOMNode.h"
27 #include "nsISelectControlFrame.h"
28 #include "nsContentUtils.h"
29 #include "nsIDocument.h"
30 #include "nsIScrollableFrame.h"
31 #include "nsListControlFrame.h"
32 #include "nsAutoPtr.h"
33 #include "nsStyleSet.h"
34 #include "nsNodeInfoManager.h"
35 #include "nsContentCreatorFunctions.h"
36 #include "nsLayoutUtils.h"
37 #include "nsDisplayList.h"
38 #include "nsITheme.h"
39 #include "nsThemeConstants.h"
40 #include "nsRenderingContext.h"
41 #include "mozilla/Likely.h"
42 #include <algorithm>
43 #include "nsTextNode.h"
44 #include "mozilla/AsyncEventDispatcher.h"
45 #include "mozilla/EventStates.h"
46 #include "mozilla/LookAndFeel.h"
47 #include "mozilla/MouseEvents.h"
48 #include "mozilla/unused.h"
50 using namespace mozilla;
51 using namespace mozilla::gfx;
53 NS_IMETHODIMP
54 nsComboboxControlFrame::RedisplayTextEvent::Run()
56 if (mControlFrame)
57 mControlFrame->HandleRedisplayTextEvent();
58 return NS_OK;
61 class nsPresState;
63 #define FIX_FOR_BUG_53259
65 // Drop down list event management.
66 // The combo box uses the following strategy for managing the drop-down list.
67 // If the combo box or its arrow button is clicked on the drop-down list is displayed
68 // If mouse exits the combo box with the drop-down list displayed the drop-down list
69 // is asked to capture events
70 // The drop-down list will capture all events including mouse down and up and will always
71 // return with ListWasSelected method call regardless of whether an item in the list was
72 // actually selected.
73 // The ListWasSelected code will turn off mouse-capture for the drop-down list.
74 // The drop-down list does not explicitly set capture when it is in the drop-down mode.
77 /**
78 * Helper class that listens to the combo boxes button. If the button is pressed the
79 * combo box is toggled to open or close. this is used by Accessibility which presses
80 * that button Programmatically.
82 class nsComboButtonListener : public nsIDOMEventListener
84 private:
85 virtual ~nsComboButtonListener() {}
87 public:
88 NS_DECL_ISUPPORTS
90 NS_IMETHOD HandleEvent(nsIDOMEvent*) MOZ_OVERRIDE
92 mComboBox->ShowDropDown(!mComboBox->IsDroppedDown());
93 return NS_OK;
96 explicit nsComboButtonListener(nsComboboxControlFrame* aCombobox)
98 mComboBox = aCombobox;
101 nsComboboxControlFrame* mComboBox;
104 NS_IMPL_ISUPPORTS(nsComboButtonListener,
105 nsIDOMEventListener)
107 // static class data member for Bug 32920
108 nsComboboxControlFrame* nsComboboxControlFrame::sFocused = nullptr;
110 nsContainerFrame*
111 NS_NewComboboxControlFrame(nsIPresShell* aPresShell, nsStyleContext* aContext, nsFrameState aStateFlags)
113 nsComboboxControlFrame* it = new (aPresShell) nsComboboxControlFrame(aContext);
115 if (it) {
116 // set the state flags (if any are provided)
117 it->AddStateBits(aStateFlags);
120 return it;
123 NS_IMPL_FRAMEARENA_HELPERS(nsComboboxControlFrame)
125 //-----------------------------------------------------------
126 // Reflow Debugging Macros
127 // These let us "see" how many reflow counts are happening
128 //-----------------------------------------------------------
129 #ifdef DO_REFLOW_COUNTER
131 #define MAX_REFLOW_CNT 1024
132 static int32_t gTotalReqs = 0;;
133 static int32_t gTotalReflows = 0;;
134 static int32_t gReflowControlCntRQ[MAX_REFLOW_CNT];
135 static int32_t gReflowControlCnt[MAX_REFLOW_CNT];
136 static int32_t gReflowInx = -1;
138 #define REFLOW_COUNTER() \
139 if (mReflowId > -1) \
140 gReflowControlCnt[mReflowId]++;
142 #define REFLOW_COUNTER_REQUEST() \
143 if (mReflowId > -1) \
144 gReflowControlCntRQ[mReflowId]++;
146 #define REFLOW_COUNTER_DUMP(__desc) \
147 if (mReflowId > -1) {\
148 gTotalReqs += gReflowControlCntRQ[mReflowId];\
149 gTotalReflows += gReflowControlCnt[mReflowId];\
150 printf("** Id:%5d %s RF: %d RQ: %d %d/%d %5.2f\n", \
151 mReflowId, (__desc), \
152 gReflowControlCnt[mReflowId], \
153 gReflowControlCntRQ[mReflowId],\
154 gTotalReflows, gTotalReqs, float(gTotalReflows)/float(gTotalReqs)*100.0f);\
157 #define REFLOW_COUNTER_INIT() \
158 if (gReflowInx < MAX_REFLOW_CNT) { \
159 gReflowInx++; \
160 mReflowId = gReflowInx; \
161 gReflowControlCnt[mReflowId] = 0; \
162 gReflowControlCntRQ[mReflowId] = 0; \
163 } else { \
164 mReflowId = -1; \
167 // reflow messages
168 #define REFLOW_DEBUG_MSG(_msg1) printf((_msg1))
169 #define REFLOW_DEBUG_MSG2(_msg1, _msg2) printf((_msg1), (_msg2))
170 #define REFLOW_DEBUG_MSG3(_msg1, _msg2, _msg3) printf((_msg1), (_msg2), (_msg3))
171 #define REFLOW_DEBUG_MSG4(_msg1, _msg2, _msg3, _msg4) printf((_msg1), (_msg2), (_msg3), (_msg4))
173 #else //-------------
175 #define REFLOW_COUNTER_REQUEST()
176 #define REFLOW_COUNTER()
177 #define REFLOW_COUNTER_DUMP(__desc)
178 #define REFLOW_COUNTER_INIT()
180 #define REFLOW_DEBUG_MSG(_msg)
181 #define REFLOW_DEBUG_MSG2(_msg1, _msg2)
182 #define REFLOW_DEBUG_MSG3(_msg1, _msg2, _msg3)
183 #define REFLOW_DEBUG_MSG4(_msg1, _msg2, _msg3, _msg4)
186 #endif
188 //------------------------------------------
189 // This is for being VERY noisy
190 //------------------------------------------
191 #ifdef DO_VERY_NOISY
192 #define REFLOW_NOISY_MSG(_msg1) printf((_msg1))
193 #define REFLOW_NOISY_MSG2(_msg1, _msg2) printf((_msg1), (_msg2))
194 #define REFLOW_NOISY_MSG3(_msg1, _msg2, _msg3) printf((_msg1), (_msg2), (_msg3))
195 #define REFLOW_NOISY_MSG4(_msg1, _msg2, _msg3, _msg4) printf((_msg1), (_msg2), (_msg3), (_msg4))
196 #else
197 #define REFLOW_NOISY_MSG(_msg)
198 #define REFLOW_NOISY_MSG2(_msg1, _msg2)
199 #define REFLOW_NOISY_MSG3(_msg1, _msg2, _msg3)
200 #define REFLOW_NOISY_MSG4(_msg1, _msg2, _msg3, _msg4)
201 #endif
203 //------------------------------------------
204 // Displays value in pixels or twips
205 //------------------------------------------
206 #ifdef DO_PIXELS
207 #define PX(__v) __v / 15
208 #else
209 #define PX(__v) __v
210 #endif
212 //------------------------------------------------------
213 //-- Done with macros
214 //------------------------------------------------------
216 nsComboboxControlFrame::nsComboboxControlFrame(nsStyleContext* aContext)
217 : nsBlockFrame(aContext)
218 , mDisplayFrame(nullptr)
219 , mButtonFrame(nullptr)
220 , mDropdownFrame(nullptr)
221 , mListControlFrame(nullptr)
222 , mDisplayWidth(0)
223 , mRecentSelectedIndex(NS_SKIP_NOTIFY_INDEX)
224 , mDisplayedIndex(-1)
225 , mLastDropDownAboveScreenY(nscoord_MIN)
226 , mLastDropDownBelowScreenY(nscoord_MIN)
227 , mDroppedDown(false)
228 , mInRedisplayText(false)
229 , mDelayedShowDropDown(false)
231 REFLOW_COUNTER_INIT()
234 //--------------------------------------------------------------
235 nsComboboxControlFrame::~nsComboboxControlFrame()
237 REFLOW_COUNTER_DUMP("nsCCF");
240 //--------------------------------------------------------------
242 NS_QUERYFRAME_HEAD(nsComboboxControlFrame)
243 NS_QUERYFRAME_ENTRY(nsIComboboxControlFrame)
244 NS_QUERYFRAME_ENTRY(nsIFormControlFrame)
245 NS_QUERYFRAME_ENTRY(nsIAnonymousContentCreator)
246 NS_QUERYFRAME_ENTRY(nsISelectControlFrame)
247 NS_QUERYFRAME_ENTRY(nsIStatefulFrame)
248 NS_QUERYFRAME_TAIL_INHERITING(nsBlockFrame)
250 #ifdef ACCESSIBILITY
251 a11y::AccType
252 nsComboboxControlFrame::AccessibleType()
254 return a11y::eHTMLComboboxType;
256 #endif
258 void
259 nsComboboxControlFrame::SetFocus(bool aOn, bool aRepaint)
261 nsWeakFrame weakFrame(this);
262 if (aOn) {
263 nsListControlFrame::ComboboxFocusSet();
264 sFocused = this;
265 if (mDelayedShowDropDown) {
266 ShowDropDown(true); // might destroy us
267 if (!weakFrame.IsAlive()) {
268 return;
271 } else {
272 sFocused = nullptr;
273 mDelayedShowDropDown = false;
274 if (mDroppedDown) {
275 mListControlFrame->ComboboxFinish(mDisplayedIndex); // might destroy us
276 if (!weakFrame.IsAlive()) {
277 return;
280 // May delete |this|.
281 mListControlFrame->FireOnChange();
284 if (!weakFrame.IsAlive()) {
285 return;
288 // This is needed on a temporary basis. It causes the focus
289 // rect to be drawn. This is much faster than ReResolvingStyle
290 // Bug 32920
291 InvalidateFrame();
294 void
295 nsComboboxControlFrame::ShowPopup(bool aShowPopup)
297 nsView* view = mDropdownFrame->GetView();
298 nsViewManager* viewManager = view->GetViewManager();
300 if (aShowPopup) {
301 nsRect rect = mDropdownFrame->GetRect();
302 rect.x = rect.y = 0;
303 viewManager->ResizeView(view, rect);
304 viewManager->SetViewVisibility(view, nsViewVisibility_kShow);
305 } else {
306 viewManager->SetViewVisibility(view, nsViewVisibility_kHide);
307 nsRect emptyRect(0, 0, 0, 0);
308 viewManager->ResizeView(view, emptyRect);
311 // fire a popup dom event
312 nsEventStatus status = nsEventStatus_eIgnore;
313 WidgetMouseEvent event(true, aShowPopup ?
314 NS_XUL_POPUP_SHOWING : NS_XUL_POPUP_HIDING, nullptr,
315 WidgetMouseEvent::eReal);
317 nsCOMPtr<nsIPresShell> shell = PresContext()->GetPresShell();
318 if (shell)
319 shell->HandleDOMEventWithTarget(mContent, &event, &status);
322 bool
323 nsComboboxControlFrame::ShowList(bool aShowList)
325 nsView* view = mDropdownFrame->GetView();
326 if (aShowList) {
327 NS_ASSERTION(!view->HasWidget(),
328 "We shouldn't have a widget before we need to display the popup");
330 // Create the widget for the drop-down list
331 view->GetViewManager()->SetViewFloating(view, true);
333 nsWidgetInitData widgetData;
334 widgetData.mWindowType = eWindowType_popup;
335 widgetData.mBorderStyle = eBorderStyle_default;
336 view->CreateWidgetForPopup(&widgetData);
337 } else {
338 nsIWidget* widget = view->GetWidget();
339 if (widget) {
340 // We must do this before ShowPopup in case it destroys us (bug 813442).
341 widget->CaptureRollupEvents(this, false);
345 nsWeakFrame weakFrame(this);
346 ShowPopup(aShowList); // might destroy us
347 if (!weakFrame.IsAlive()) {
348 return false;
351 mDroppedDown = aShowList;
352 nsIWidget* widget = view->GetWidget();
353 if (mDroppedDown) {
354 // The listcontrol frame will call back to the nsComboboxControlFrame's
355 // ListWasSelected which will stop the capture.
356 mListControlFrame->AboutToDropDown();
357 mListControlFrame->CaptureMouseEvents(true);
358 if (widget) {
359 widget->CaptureRollupEvents(this, true);
361 } else {
362 if (widget) {
363 view->DestroyWidget();
367 return weakFrame.IsAlive();
370 class nsResizeDropdownAtFinalPosition MOZ_FINAL
371 : public nsIReflowCallback, public nsRunnable
373 public:
374 explicit nsResizeDropdownAtFinalPosition(nsComboboxControlFrame* aFrame)
375 : mFrame(aFrame)
377 MOZ_COUNT_CTOR(nsResizeDropdownAtFinalPosition);
380 protected:
381 ~nsResizeDropdownAtFinalPosition()
383 MOZ_COUNT_DTOR(nsResizeDropdownAtFinalPosition);
386 public:
387 virtual bool ReflowFinished() MOZ_OVERRIDE
389 Run();
390 NS_RELEASE_THIS();
391 return false;
394 virtual void ReflowCallbackCanceled() MOZ_OVERRIDE
396 NS_RELEASE_THIS();
399 NS_IMETHODIMP Run() MOZ_OVERRIDE
401 if (mFrame.IsAlive()) {
402 static_cast<nsComboboxControlFrame*>(mFrame.GetFrame())->
403 AbsolutelyPositionDropDown();
405 return NS_OK;
408 nsWeakFrame mFrame;
411 void
412 nsComboboxControlFrame::ReflowDropdown(nsPresContext* aPresContext,
413 const nsHTMLReflowState& aReflowState)
415 // All we want out of it later on, really, is the height of a row, so we
416 // don't even need to cache mDropdownFrame's ascent or anything. If we don't
417 // need to reflow it, just bail out here.
418 if (!aReflowState.ShouldReflowAllKids() &&
419 !NS_SUBTREE_DIRTY(mDropdownFrame)) {
420 return;
423 // XXXbz this will, for small-height dropdowns, have extra space on the right
424 // edge for the scrollbar we don't show... but that's the best we can do here
425 // for now.
426 WritingMode wm = mDropdownFrame->GetWritingMode();
427 LogicalSize availSize = aReflowState.AvailableSize(wm);
428 availSize.BSize(wm) = NS_UNCONSTRAINEDSIZE;
429 nsHTMLReflowState kidReflowState(aPresContext, aReflowState, mDropdownFrame,
430 availSize);
432 // If the dropdown's intrinsic width is narrower than our specified width,
433 // then expand it out. We want our border-box width to end up the same as
434 // the dropdown's so account for both sets of mComputedBorderPadding.
435 nscoord forcedWidth = aReflowState.ComputedWidth() +
436 aReflowState.ComputedPhysicalBorderPadding().LeftRight() -
437 kidReflowState.ComputedPhysicalBorderPadding().LeftRight();
438 kidReflowState.SetComputedWidth(std::max(kidReflowState.ComputedWidth(),
439 forcedWidth));
441 // ensure we start off hidden
442 if (GetStateBits() & NS_FRAME_FIRST_REFLOW) {
443 nsView* view = mDropdownFrame->GetView();
444 nsViewManager* viewManager = view->GetViewManager();
445 viewManager->SetViewVisibility(view, nsViewVisibility_kHide);
446 nsRect emptyRect(0, 0, 0, 0);
447 viewManager->ResizeView(view, emptyRect);
450 // Allow the child to move/size/change-visibility its view if it's currently
451 // dropped down
452 int32_t flags = NS_FRAME_NO_MOVE_FRAME | NS_FRAME_NO_VISIBILITY | NS_FRAME_NO_SIZE_VIEW;
453 if (mDroppedDown) {
454 flags = 0;
456 nsRect rect = mDropdownFrame->GetRect();
457 nsHTMLReflowMetrics desiredSize(aReflowState);
458 nsReflowStatus ignoredStatus;
459 ReflowChild(mDropdownFrame, aPresContext, desiredSize,
460 kidReflowState, rect.x, rect.y, flags, ignoredStatus);
462 // Set the child's width and height to its desired size
463 FinishReflowChild(mDropdownFrame, aPresContext, desiredSize,
464 &kidReflowState, rect.x, rect.y, flags);
467 nsPoint
468 nsComboboxControlFrame::GetCSSTransformTranslation()
470 nsIFrame* frame = this;
471 bool is3DTransform = false;
472 Matrix transform;
473 while (frame) {
474 nsIFrame* parent;
475 Matrix4x4 ctm = frame->GetTransformMatrix(nullptr, &parent);
476 Matrix matrix;
477 if (ctm.Is2D(&matrix)) {
478 transform = transform * matrix;
479 } else {
480 is3DTransform = true;
481 break;
483 frame = parent;
485 nsPoint translation;
486 if (!is3DTransform && !transform.HasNonTranslation()) {
487 nsPresContext* pc = PresContext();
488 // To get the translation introduced only by transforms we subtract the
489 // regular non-transform translation.
490 nsRootPresContext* rootPC = pc->GetRootPresContext();
491 if (rootPC) {
492 int32_t apd = pc->AppUnitsPerDevPixel();
493 translation.x = NSFloatPixelsToAppUnits(transform._31, apd);
494 translation.y = NSFloatPixelsToAppUnits(transform._32, apd);
495 translation -= GetOffsetToCrossDoc(rootPC->PresShell()->GetRootFrame());
498 return translation;
501 class nsAsyncRollup : public nsRunnable
503 public:
504 explicit nsAsyncRollup(nsComboboxControlFrame* aFrame) : mFrame(aFrame) {}
505 NS_IMETHODIMP Run()
507 if (mFrame.IsAlive()) {
508 static_cast<nsComboboxControlFrame*>(mFrame.GetFrame())
509 ->RollupFromList();
511 return NS_OK;
513 nsWeakFrame mFrame;
516 class nsAsyncResize : public nsRunnable
518 public:
519 explicit nsAsyncResize(nsComboboxControlFrame* aFrame) : mFrame(aFrame) {}
520 NS_IMETHODIMP Run()
522 if (mFrame.IsAlive()) {
523 nsComboboxControlFrame* combo =
524 static_cast<nsComboboxControlFrame*>(mFrame.GetFrame());
525 static_cast<nsListControlFrame*>(combo->mDropdownFrame)->
526 SetSuppressScrollbarUpdate(true);
527 nsCOMPtr<nsIPresShell> shell = mFrame->PresContext()->PresShell();
528 shell->FrameNeedsReflow(combo->mDropdownFrame, nsIPresShell::eResize,
529 NS_FRAME_IS_DIRTY);
530 shell->FlushPendingNotifications(Flush_Layout);
531 if (mFrame.IsAlive()) {
532 combo = static_cast<nsComboboxControlFrame*>(mFrame.GetFrame());
533 static_cast<nsListControlFrame*>(combo->mDropdownFrame)->
534 SetSuppressScrollbarUpdate(false);
535 if (combo->mDelayedShowDropDown) {
536 combo->ShowDropDown(true);
540 return NS_OK;
542 nsWeakFrame mFrame;
545 void
546 nsComboboxControlFrame::GetAvailableDropdownSpace(nscoord* aAbove,
547 nscoord* aBelow,
548 nsPoint* aTranslation)
550 // Note: At first glance, it appears that you could simply get the absolute
551 // bounding box for the dropdown list by first getting its view, then getting
552 // the view's nsIWidget, then asking the nsIWidget for its AbsoluteBounds.
553 // The problem with this approach, is that the dropdown lists y location can
554 // change based on whether the dropdown is placed below or above the display
555 // frame. The approach, taken here is to get the absolute position of the
556 // display frame and use its location to determine if the dropdown will go
557 // offscreen.
559 // Normal frame geometry (eg GetOffsetTo, mRect) doesn't include transforms.
560 // In the special case that our transform is only a 2D translation we
561 // introduce this hack so that the dropdown will show up in the right place.
562 *aTranslation = GetCSSTransformTranslation();
563 *aAbove = 0;
564 *aBelow = 0;
566 nsRect screen = nsFormControlFrame::GetUsableScreenRect(PresContext());
567 if (mLastDropDownBelowScreenY == nscoord_MIN) {
568 nsRect thisScreenRect = GetScreenRectInAppUnits();
569 mLastDropDownBelowScreenY = thisScreenRect.YMost() + aTranslation->y;
570 mLastDropDownAboveScreenY = thisScreenRect.y + aTranslation->y;
573 nscoord minY;
574 nsPresContext* pc = PresContext()->GetToplevelContentDocumentPresContext();
575 nsIFrame* root = pc ? pc->PresShell()->GetRootFrame() : nullptr;
576 if (root) {
577 minY = root->GetScreenRectInAppUnits().y;
578 if (mLastDropDownBelowScreenY < minY) {
579 // Don't allow the drop-down to be placed above the content area.
580 return;
582 } else {
583 minY = screen.y;
586 nscoord below = screen.YMost() - mLastDropDownBelowScreenY;
587 nscoord above = mLastDropDownAboveScreenY - minY;
589 // If the difference between the space above and below is less
590 // than a row-height, then we favor the space below.
591 if (above >= below) {
592 nsListControlFrame* lcf = static_cast<nsListControlFrame*>(mDropdownFrame);
593 nscoord rowHeight = lcf->GetHeightOfARow();
594 if (above < below + rowHeight) {
595 above -= rowHeight;
599 *aBelow = below;
600 *aAbove = above;
603 nsComboboxControlFrame::DropDownPositionState
604 nsComboboxControlFrame::AbsolutelyPositionDropDown()
606 nsPoint translation;
607 nscoord above, below;
608 mLastDropDownBelowScreenY = nscoord_MIN;
609 GetAvailableDropdownSpace(&above, &below, &translation);
610 if (above <= 0 && below <= 0) {
611 if (IsDroppedDown()) {
612 // Hide the view immediately to minimize flicker.
613 nsView* view = mDropdownFrame->GetView();
614 view->GetViewManager()->SetViewVisibility(view, nsViewVisibility_kHide);
615 NS_DispatchToCurrentThread(new nsAsyncRollup(this));
617 return eDropDownPositionSuppressed;
620 nsSize dropdownSize = mDropdownFrame->GetSize();
621 nscoord height = std::max(above, below);
622 nsListControlFrame* lcf = static_cast<nsListControlFrame*>(mDropdownFrame);
623 if (height < dropdownSize.height) {
624 if (lcf->GetNumDisplayRows() > 1) {
625 // The drop-down doesn't fit and currently shows more than 1 row -
626 // schedule a resize to show fewer rows.
627 NS_DispatchToCurrentThread(new nsAsyncResize(this));
628 return eDropDownPositionPendingResize;
630 } else if (height > (dropdownSize.height + lcf->GetHeightOfARow() * 1.5) &&
631 lcf->GetDropdownCanGrow()) {
632 // The drop-down fits but there is room for at least 1.5 more rows -
633 // schedule a resize to show more rows if it has more rows to show.
634 // (1.5 rows for good measure to avoid any rounding issues that would
635 // lead to a loop of reflow requests)
636 NS_DispatchToCurrentThread(new nsAsyncResize(this));
637 return eDropDownPositionPendingResize;
640 // Position the drop-down below if there is room, otherwise place it above
641 // if there is room. If there is no room for it on either side then place
642 // it below (to avoid overlapping UI like the URL bar).
643 bool b = dropdownSize.height <= below || dropdownSize.height > above;
644 nsPoint dropdownPosition(0, b ? GetRect().height : -dropdownSize.height);
645 if (StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL) {
646 // Align the right edge of the drop-down with the right edge of the control.
647 dropdownPosition.x = GetRect().width - dropdownSize.width;
650 // Don't position the view unless the position changed since it might cause
651 // a call to NotifyGeometryChange() and an infinite loop here.
652 const nsPoint currentPos = mDropdownFrame->GetPosition();
653 const nsPoint newPos = dropdownPosition + translation;
654 if (currentPos != newPos) {
655 mDropdownFrame->SetPosition(newPos);
656 nsContainerFrame::PositionFrameView(mDropdownFrame);
658 return eDropDownPositionFinal;
661 void
662 nsComboboxControlFrame::NotifyGeometryChange()
664 // We don't need to resize if we're not dropped down since ShowDropDown
665 // does that, or if we're dirty then the reflow callback does it,
666 // or if we have a delayed ShowDropDown pending.
667 if (IsDroppedDown() &&
668 !(GetStateBits() & NS_FRAME_IS_DIRTY) &&
669 !mDelayedShowDropDown) {
670 // Async because we're likely in a middle of a scroll here so
671 // frame/view positions are in flux.
672 nsRefPtr<nsResizeDropdownAtFinalPosition> resize =
673 new nsResizeDropdownAtFinalPosition(this);
674 NS_DispatchToCurrentThread(resize);
678 //----------------------------------------------------------
680 //----------------------------------------------------------
681 #ifdef DO_REFLOW_DEBUG
682 static int myCounter = 0;
684 static void printSize(char * aDesc, nscoord aSize)
686 printf(" %s: ", aDesc);
687 if (aSize == NS_UNCONSTRAINEDSIZE) {
688 printf("UC");
689 } else {
690 printf("%d", PX(aSize));
693 #endif
695 //-------------------------------------------------------------------
696 //-- Main Reflow for the Combobox
697 //-------------------------------------------------------------------
699 nscoord
700 nsComboboxControlFrame::GetIntrinsicISize(nsRenderingContext* aRenderingContext,
701 nsLayoutUtils::IntrinsicISizeType aType)
703 // get the scrollbar width, we'll use this later
704 nscoord scrollbarWidth = 0;
705 nsPresContext* presContext = PresContext();
706 if (mListControlFrame) {
707 nsIScrollableFrame* scrollable = do_QueryFrame(mListControlFrame);
708 NS_ASSERTION(scrollable, "List must be a scrollable frame");
709 scrollbarWidth = scrollable->GetNondisappearingScrollbarWidth(
710 presContext, aRenderingContext);
713 nscoord displayWidth = 0;
714 if (MOZ_LIKELY(mDisplayFrame)) {
715 displayWidth = nsLayoutUtils::IntrinsicForContainer(aRenderingContext,
716 mDisplayFrame,
717 aType);
720 if (mDropdownFrame) {
721 nscoord dropdownContentWidth;
722 bool isUsingOverlayScrollbars =
723 LookAndFeel::GetInt(LookAndFeel::eIntID_UseOverlayScrollbars) != 0;
724 if (aType == nsLayoutUtils::MIN_ISIZE) {
725 dropdownContentWidth = mDropdownFrame->GetMinISize(aRenderingContext);
726 if (isUsingOverlayScrollbars) {
727 dropdownContentWidth += scrollbarWidth;
729 } else {
730 NS_ASSERTION(aType == nsLayoutUtils::PREF_ISIZE, "Unexpected type");
731 dropdownContentWidth = mDropdownFrame->GetPrefISize(aRenderingContext);
732 if (isUsingOverlayScrollbars) {
733 dropdownContentWidth += scrollbarWidth;
736 dropdownContentWidth = NSCoordSaturatingSubtract(dropdownContentWidth,
737 scrollbarWidth,
738 nscoord_MAX);
740 displayWidth = std::max(dropdownContentWidth, displayWidth);
743 // add room for the dropmarker button if there is one
744 if ((!IsThemed() ||
745 presContext->GetTheme()->ThemeNeedsComboboxDropmarker()) &&
746 StyleDisplay()->mAppearance != NS_THEME_NONE) {
747 displayWidth += scrollbarWidth;
750 return displayWidth;
754 nscoord
755 nsComboboxControlFrame::GetMinISize(nsRenderingContext *aRenderingContext)
757 nscoord minWidth;
758 DISPLAY_MIN_WIDTH(this, minWidth);
759 minWidth = GetIntrinsicISize(aRenderingContext, nsLayoutUtils::MIN_ISIZE);
760 return minWidth;
763 nscoord
764 nsComboboxControlFrame::GetPrefISize(nsRenderingContext *aRenderingContext)
766 nscoord prefWidth;
767 DISPLAY_PREF_WIDTH(this, prefWidth);
768 prefWidth = GetIntrinsicISize(aRenderingContext, nsLayoutUtils::PREF_ISIZE);
769 return prefWidth;
772 void
773 nsComboboxControlFrame::Reflow(nsPresContext* aPresContext,
774 nsHTMLReflowMetrics& aDesiredSize,
775 const nsHTMLReflowState& aReflowState,
776 nsReflowStatus& aStatus)
778 // Constraints we try to satisfy:
780 // 1) Default width of button is the vertical scrollbar size
781 // 2) If the width of button is bigger than our width, set width of
782 // button to 0.
783 // 3) Default height of button is height of display area
784 // 4) Width of display area is whatever is left over from our width after
785 // allocating width for the button.
786 // 5) Height of display area is GetHeightOfARow() on the
787 // mListControlFrame.
789 if (!mDisplayFrame || !mButtonFrame || !mDropdownFrame) {
790 NS_ERROR("Why did the frame constructor allow this to happen? Fix it!!");
791 return;
794 // Make sure the displayed text is the same as the selected option, bug 297389.
795 int32_t selectedIndex;
796 nsAutoString selectedOptionText;
797 if (!mDroppedDown) {
798 selectedIndex = mListControlFrame->GetSelectedIndex();
800 else {
801 // In dropped down mode the "selected index" is the hovered menu item,
802 // we want the last selected item which is |mDisplayedIndex| in this case.
803 selectedIndex = mDisplayedIndex;
805 if (selectedIndex != -1) {
806 mListControlFrame->GetOptionText(selectedIndex, selectedOptionText);
808 if (mDisplayedOptionText != selectedOptionText) {
809 RedisplayText(selectedIndex);
812 // First reflow our dropdown so that we know how tall we should be.
813 ReflowDropdown(aPresContext, aReflowState);
814 nsRefPtr<nsResizeDropdownAtFinalPosition> resize =
815 new nsResizeDropdownAtFinalPosition(this);
816 if (NS_SUCCEEDED(aPresContext->PresShell()->PostReflowCallback(resize))) {
817 // The reflow callback queue doesn't AddRef so we keep it alive until
818 // it's released in its ReflowFinished / ReflowCallbackCanceled.
819 unused << resize.forget();
822 // Get the width of the vertical scrollbar. That will be the width of the
823 // dropdown button.
824 nscoord buttonWidth;
825 const nsStyleDisplay *disp = StyleDisplay();
826 if ((IsThemed(disp) && !aPresContext->GetTheme()->ThemeNeedsComboboxDropmarker()) ||
827 StyleDisplay()->mAppearance == NS_THEME_NONE) {
828 buttonWidth = 0;
830 else {
831 nsIScrollableFrame* scrollable = do_QueryFrame(mListControlFrame);
832 NS_ASSERTION(scrollable, "List must be a scrollable frame");
833 buttonWidth = scrollable->GetNondisappearingScrollbarWidth(
834 PresContext(), aReflowState.rendContext);
835 if (buttonWidth > aReflowState.ComputedWidth()) {
836 buttonWidth = 0;
840 mDisplayWidth = aReflowState.ComputedWidth() - buttonWidth;
842 nsBlockFrame::Reflow(aPresContext, aDesiredSize, aReflowState, aStatus);
844 // The button should occupy the same space as a scrollbar
845 nsRect buttonRect = mButtonFrame->GetRect();
847 if (StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL) {
848 buttonRect.x = aReflowState.ComputedPhysicalBorderPadding().left -
849 aReflowState.ComputedPhysicalPadding().left;
851 else {
852 buttonRect.x = aReflowState.ComputedPhysicalBorderPadding().LeftRight() +
853 mDisplayWidth -
854 (aReflowState.ComputedPhysicalBorderPadding().right -
855 aReflowState.ComputedPhysicalPadding().right);
857 buttonRect.width = buttonWidth;
859 buttonRect.y = this->GetUsedBorder().top;
860 buttonRect.height = mDisplayFrame->GetRect().height +
861 this->GetUsedPadding().TopBottom();
863 mButtonFrame->SetRect(buttonRect);
865 if (!NS_INLINE_IS_BREAK_BEFORE(aStatus) &&
866 !NS_FRAME_IS_FULLY_COMPLETE(aStatus)) {
867 // This frame didn't fit inside a fragmentation container. Splitting
868 // a nsComboboxControlFrame makes no sense, so we override the status here.
869 aStatus = NS_FRAME_COMPLETE;
873 //--------------------------------------------------------------
875 nsIAtom*
876 nsComboboxControlFrame::GetType() const
878 return nsGkAtoms::comboboxControlFrame;
881 #ifdef DEBUG_FRAME_DUMP
882 nsresult
883 nsComboboxControlFrame::GetFrameName(nsAString& aResult) const
885 return MakeFrameName(NS_LITERAL_STRING("ComboboxControl"), aResult);
887 #endif
890 //----------------------------------------------------------------------
891 // nsIComboboxControlFrame
892 //----------------------------------------------------------------------
893 void
894 nsComboboxControlFrame::ShowDropDown(bool aDoDropDown)
896 mDelayedShowDropDown = false;
897 EventStates eventStates = mContent->AsElement()->State();
898 if (aDoDropDown && eventStates.HasState(NS_EVENT_STATE_DISABLED)) {
899 return;
902 if (!mDroppedDown && aDoDropDown) {
903 nsFocusManager* fm = nsFocusManager::GetFocusManager();
904 if (!fm || fm->GetFocusedContent() == GetContent()) {
905 DropDownPositionState state = AbsolutelyPositionDropDown();
906 if (state == eDropDownPositionFinal) {
907 ShowList(aDoDropDown); // might destroy us
908 } else if (state == eDropDownPositionPendingResize) {
909 // Delay until after the resize reflow, see nsAsyncResize.
910 mDelayedShowDropDown = true;
912 } else {
913 // Delay until we get focus, see SetFocus().
914 mDelayedShowDropDown = true;
916 } else if (mDroppedDown && !aDoDropDown) {
917 ShowList(aDoDropDown); // might destroy us
921 void
922 nsComboboxControlFrame::SetDropDown(nsIFrame* aDropDownFrame)
924 mDropdownFrame = aDropDownFrame;
925 mListControlFrame = do_QueryFrame(mDropdownFrame);
928 nsIFrame*
929 nsComboboxControlFrame::GetDropDown()
931 return mDropdownFrame;
934 ///////////////////////////////////////////////////////////////
936 NS_IMETHODIMP
937 nsComboboxControlFrame::RedisplaySelectedText()
939 nsAutoScriptBlocker scriptBlocker;
940 return RedisplayText(mListControlFrame->GetSelectedIndex());
943 nsresult
944 nsComboboxControlFrame::RedisplayText(int32_t aIndex)
946 // Get the text to display
947 if (aIndex != -1) {
948 mListControlFrame->GetOptionText(aIndex, mDisplayedOptionText);
949 } else {
950 mDisplayedOptionText.Truncate();
952 mDisplayedIndex = aIndex;
954 REFLOW_DEBUG_MSG2("RedisplayText \"%s\"\n",
955 NS_LossyConvertUTF16toASCII(mDisplayedOptionText).get());
957 // Send reflow command because the new text maybe larger
958 nsresult rv = NS_OK;
959 if (mDisplayContent) {
960 // Don't call ActuallyDisplayText(true) directly here since that
961 // could cause recursive frame construction. See bug 283117 and the comment in
962 // HandleRedisplayTextEvent() below.
964 // Revoke outstanding events to avoid out-of-order events which could mean
965 // displaying the wrong text.
966 mRedisplayTextEvent.Revoke();
968 NS_ASSERTION(!nsContentUtils::IsSafeToRunScript(),
969 "If we happen to run our redisplay event now, we might kill "
970 "ourselves!");
972 nsRefPtr<RedisplayTextEvent> event = new RedisplayTextEvent(this);
973 mRedisplayTextEvent = event;
974 if (!nsContentUtils::AddScriptRunner(event))
975 mRedisplayTextEvent.Forget();
977 return rv;
980 void
981 nsComboboxControlFrame::HandleRedisplayTextEvent()
983 // First, make sure that the content model is up to date and we've
984 // constructed the frames for all our content in the right places.
985 // Otherwise they'll end up under the wrong insertion frame when we
986 // ActuallyDisplayText, since that flushes out the content sink by
987 // calling SetText on a DOM node with aNotify set to true. See bug
988 // 289730.
989 nsWeakFrame weakThis(this);
990 PresContext()->Document()->
991 FlushPendingNotifications(Flush_ContentAndNotify);
992 if (!weakThis.IsAlive())
993 return;
995 // Redirect frame insertions during this method (see GetContentInsertionFrame())
996 // so that any reframing that the frame constructor forces upon us is inserted
997 // into the correct parent (mDisplayFrame). See bug 282607.
998 NS_PRECONDITION(!mInRedisplayText, "Nested RedisplayText");
999 mInRedisplayText = true;
1000 mRedisplayTextEvent.Forget();
1002 ActuallyDisplayText(true);
1003 // XXXbz This should perhaps be eResize. Check.
1004 PresContext()->PresShell()->FrameNeedsReflow(mDisplayFrame,
1005 nsIPresShell::eStyleChange,
1006 NS_FRAME_IS_DIRTY);
1008 mInRedisplayText = false;
1011 void
1012 nsComboboxControlFrame::ActuallyDisplayText(bool aNotify)
1014 if (mDisplayedOptionText.IsEmpty()) {
1015 // Have to use a non-breaking space for line-height calculations
1016 // to be right
1017 static const char16_t space = 0xA0;
1018 mDisplayContent->SetText(&space, 1, aNotify);
1019 } else {
1020 mDisplayContent->SetText(mDisplayedOptionText, aNotify);
1024 int32_t
1025 nsComboboxControlFrame::GetIndexOfDisplayArea()
1027 return mDisplayedIndex;
1030 //----------------------------------------------------------------------
1031 // nsISelectControlFrame
1032 //----------------------------------------------------------------------
1033 NS_IMETHODIMP
1034 nsComboboxControlFrame::DoneAddingChildren(bool aIsDone)
1036 nsISelectControlFrame* listFrame = do_QueryFrame(mDropdownFrame);
1037 if (!listFrame)
1038 return NS_ERROR_FAILURE;
1040 return listFrame->DoneAddingChildren(aIsDone);
1043 NS_IMETHODIMP
1044 nsComboboxControlFrame::AddOption(int32_t aIndex)
1046 if (aIndex <= mDisplayedIndex) {
1047 ++mDisplayedIndex;
1050 nsListControlFrame* lcf = static_cast<nsListControlFrame*>(mDropdownFrame);
1051 return lcf->AddOption(aIndex);
1055 NS_IMETHODIMP
1056 nsComboboxControlFrame::RemoveOption(int32_t aIndex)
1058 nsWeakFrame weakThis(this);
1059 if (mListControlFrame->GetNumberOfOptions() > 0) {
1060 if (aIndex < mDisplayedIndex) {
1061 --mDisplayedIndex;
1062 } else if (aIndex == mDisplayedIndex) {
1063 mDisplayedIndex = 0; // IE6 compat
1064 RedisplayText(mDisplayedIndex);
1067 else {
1068 // If we removed the last option, we need to blank things out
1069 RedisplayText(-1);
1072 if (!weakThis.IsAlive())
1073 return NS_OK;
1075 nsListControlFrame* lcf = static_cast<nsListControlFrame*>(mDropdownFrame);
1076 return lcf->RemoveOption(aIndex);
1079 NS_IMETHODIMP
1080 nsComboboxControlFrame::OnSetSelectedIndex(int32_t aOldIndex, int32_t aNewIndex)
1082 nsAutoScriptBlocker scriptBlocker;
1083 RedisplayText(aNewIndex);
1084 NS_ASSERTION(mDropdownFrame, "No dropdown frame!");
1086 nsISelectControlFrame* listFrame = do_QueryFrame(mDropdownFrame);
1087 NS_ASSERTION(listFrame, "No list frame!");
1089 return listFrame->OnSetSelectedIndex(aOldIndex, aNewIndex);
1092 // End nsISelectControlFrame
1093 //----------------------------------------------------------------------
1095 nsresult
1096 nsComboboxControlFrame::HandleEvent(nsPresContext* aPresContext,
1097 WidgetGUIEvent* aEvent,
1098 nsEventStatus* aEventStatus)
1100 NS_ENSURE_ARG_POINTER(aEventStatus);
1102 if (nsEventStatus_eConsumeNoDefault == *aEventStatus) {
1103 return NS_OK;
1106 EventStates eventStates = mContent->AsElement()->State();
1107 if (eventStates.HasState(NS_EVENT_STATE_DISABLED)) {
1108 return NS_OK;
1111 // If we have style that affects how we are selected, feed event down to
1112 // nsFrame::HandleEvent so that selection takes place when appropriate.
1113 const nsStyleUserInterface* uiStyle = StyleUserInterface();
1114 if (uiStyle->mUserInput == NS_STYLE_USER_INPUT_NONE || uiStyle->mUserInput == NS_STYLE_USER_INPUT_DISABLED)
1115 return nsBlockFrame::HandleEvent(aPresContext, aEvent, aEventStatus);
1117 return NS_OK;
1121 nsresult
1122 nsComboboxControlFrame::SetFormProperty(nsIAtom* aName, const nsAString& aValue)
1124 nsIFormControlFrame* fcFrame = do_QueryFrame(mDropdownFrame);
1125 if (!fcFrame) {
1126 return NS_NOINTERFACE;
1129 return fcFrame->SetFormProperty(aName, aValue);
1132 nsContainerFrame*
1133 nsComboboxControlFrame::GetContentInsertionFrame() {
1134 return mInRedisplayText ? mDisplayFrame : mDropdownFrame->GetContentInsertionFrame();
1137 nsresult
1138 nsComboboxControlFrame::CreateAnonymousContent(nsTArray<ContentInfo>& aElements)
1140 // The frames used to display the combo box and the button used to popup the dropdown list
1141 // are created through anonymous content. The dropdown list is not created through anonymous
1142 // content because its frame is initialized specifically for the drop-down case and it is placed
1143 // a special list referenced through NS_COMBO_FRAME_POPUP_LIST_INDEX to keep separate from the
1144 // layout of the display and button.
1146 // Note: The value attribute of the display content is set when an item is selected in the dropdown list.
1147 // If the content specified below does not honor the value attribute than nothing will be displayed.
1149 // For now the content that is created corresponds to two input buttons. It would be better to create the
1150 // tag as something other than input, but then there isn't any way to create a button frame since it
1151 // isn't possible to set the display type in CSS2 to create a button frame.
1153 // create content used for display
1154 //nsIAtom* tag = NS_NewAtom("mozcombodisplay");
1156 // Add a child text content node for the label
1158 nsNodeInfoManager *nimgr = mContent->NodeInfo()->NodeInfoManager();
1160 mDisplayContent = new nsTextNode(nimgr);
1162 // set the value of the text node
1163 mDisplayedIndex = mListControlFrame->GetSelectedIndex();
1164 if (mDisplayedIndex != -1) {
1165 mListControlFrame->GetOptionText(mDisplayedIndex, mDisplayedOptionText);
1167 ActuallyDisplayText(false);
1169 if (!aElements.AppendElement(mDisplayContent))
1170 return NS_ERROR_OUT_OF_MEMORY;
1172 mButtonContent = mContent->OwnerDoc()->CreateHTMLElement(nsGkAtoms::button);
1173 if (!mButtonContent)
1174 return NS_ERROR_OUT_OF_MEMORY;
1176 // make someone to listen to the button. If its pressed by someone like Accessibility
1177 // then open or close the combo box.
1178 mButtonListener = new nsComboButtonListener(this);
1179 mButtonContent->AddEventListener(NS_LITERAL_STRING("click"), mButtonListener,
1180 false, false);
1182 mButtonContent->SetAttr(kNameSpaceID_None, nsGkAtoms::type,
1183 NS_LITERAL_STRING("button"), false);
1184 // Set tabindex="-1" so that the button is not tabbable
1185 mButtonContent->SetAttr(kNameSpaceID_None, nsGkAtoms::tabindex,
1186 NS_LITERAL_STRING("-1"), false);
1188 if (!aElements.AppendElement(mButtonContent))
1189 return NS_ERROR_OUT_OF_MEMORY;
1191 return NS_OK;
1194 void
1195 nsComboboxControlFrame::AppendAnonymousContentTo(nsTArray<nsIContent*>& aElements,
1196 uint32_t aFilter)
1198 if (mDisplayContent) {
1199 aElements.AppendElement(mDisplayContent);
1202 if (mButtonContent) {
1203 aElements.AppendElement(mButtonContent);
1207 // XXXbz this is a for-now hack. Now that display:inline-block works,
1208 // need to revisit this.
1209 class nsComboboxDisplayFrame : public nsBlockFrame {
1210 public:
1211 NS_DECL_FRAMEARENA_HELPERS
1213 nsComboboxDisplayFrame (nsStyleContext* aContext,
1214 nsComboboxControlFrame* aComboBox)
1215 : nsBlockFrame(aContext),
1216 mComboBox(aComboBox)
1219 // Need this so that line layout knows that this block's width
1220 // depends on the available width.
1221 virtual nsIAtom* GetType() const MOZ_OVERRIDE;
1223 virtual bool IsFrameOfType(uint32_t aFlags) const MOZ_OVERRIDE
1225 return nsBlockFrame::IsFrameOfType(aFlags &
1226 ~(nsIFrame::eReplacedContainsBlock));
1229 virtual void Reflow(nsPresContext* aPresContext,
1230 nsHTMLReflowMetrics& aDesiredSize,
1231 const nsHTMLReflowState& aReflowState,
1232 nsReflowStatus& aStatus) MOZ_OVERRIDE;
1234 virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder,
1235 const nsRect& aDirtyRect,
1236 const nsDisplayListSet& aLists) MOZ_OVERRIDE;
1238 protected:
1239 nsComboboxControlFrame* mComboBox;
1242 NS_IMPL_FRAMEARENA_HELPERS(nsComboboxDisplayFrame)
1244 nsIAtom*
1245 nsComboboxDisplayFrame::GetType() const
1247 return nsGkAtoms::comboboxDisplayFrame;
1250 void
1251 nsComboboxDisplayFrame::Reflow(nsPresContext* aPresContext,
1252 nsHTMLReflowMetrics& aDesiredSize,
1253 const nsHTMLReflowState& aReflowState,
1254 nsReflowStatus& aStatus)
1256 nsHTMLReflowState state(aReflowState);
1257 if (state.ComputedHeight() == NS_INTRINSICSIZE) {
1258 // Note that the only way we can have a computed height here is if the
1259 // combobox had a specified height. If it didn't, size based on what our
1260 // rows look like, for lack of anything better.
1261 state.SetComputedHeight(mComboBox->mListControlFrame->GetHeightOfARow());
1263 nscoord computedWidth = mComboBox->mDisplayWidth -
1264 state.ComputedPhysicalBorderPadding().LeftRight();
1265 if (computedWidth < 0) {
1266 computedWidth = 0;
1268 state.SetComputedWidth(computedWidth);
1270 return nsBlockFrame::Reflow(aPresContext, aDesiredSize, state, aStatus);
1273 void
1274 nsComboboxDisplayFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
1275 const nsRect& aDirtyRect,
1276 const nsDisplayListSet& aLists)
1278 nsDisplayListCollection set;
1279 nsBlockFrame::BuildDisplayList(aBuilder, aDirtyRect, set);
1281 // remove background items if parent frame is themed
1282 if (mComboBox->IsThemed()) {
1283 set.BorderBackground()->DeleteAll();
1286 set.MoveTo(aLists);
1289 nsIFrame*
1290 nsComboboxControlFrame::CreateFrameFor(nsIContent* aContent)
1292 NS_PRECONDITION(nullptr != aContent, "null ptr");
1294 NS_ASSERTION(mDisplayContent, "mDisplayContent can't be null!");
1296 if (mDisplayContent != aContent) {
1297 // We only handle the frames for mDisplayContent here
1298 return nullptr;
1301 // Get PresShell
1302 nsIPresShell *shell = PresContext()->PresShell();
1303 nsStyleSet *styleSet = shell->StyleSet();
1305 // create the style contexts for the anonymous block frame and text frame
1306 nsRefPtr<nsStyleContext> styleContext;
1307 styleContext = styleSet->
1308 ResolveAnonymousBoxStyle(nsCSSAnonBoxes::mozDisplayComboboxControlFrame,
1309 mStyleContext);
1311 nsRefPtr<nsStyleContext> textStyleContext;
1312 textStyleContext = styleSet->ResolveStyleForNonElement(mStyleContext);
1314 // Start by creating our anonymous block frame
1315 mDisplayFrame = new (shell) nsComboboxDisplayFrame(styleContext, this);
1316 mDisplayFrame->Init(mContent, this, nullptr);
1318 // Create a text frame and put it inside the block frame
1319 nsIFrame* textFrame = NS_NewTextFrame(shell, textStyleContext);
1321 // initialize the text frame
1322 textFrame->Init(aContent, mDisplayFrame, nullptr);
1323 mDisplayContent->SetPrimaryFrame(textFrame);
1325 nsFrameList textList(textFrame, textFrame);
1326 mDisplayFrame->SetInitialChildList(kPrincipalList, textList);
1327 return mDisplayFrame;
1330 void
1331 nsComboboxControlFrame::DestroyFrom(nsIFrame* aDestructRoot)
1333 // Revoke any pending RedisplayTextEvent
1334 mRedisplayTextEvent.Revoke();
1336 nsFormControlFrame::RegUnRegAccessKey(static_cast<nsIFrame*>(this), false);
1338 if (mDroppedDown) {
1339 MOZ_ASSERT(mDropdownFrame, "mDroppedDown without frame");
1340 nsView* view = mDropdownFrame->GetView();
1341 MOZ_ASSERT(view);
1342 nsIWidget* widget = view->GetWidget();
1343 if (widget) {
1344 widget->CaptureRollupEvents(this, false);
1348 // Cleanup frames in popup child list
1349 mPopupFrames.DestroyFramesFrom(aDestructRoot);
1350 nsContentUtils::DestroyAnonymousContent(&mDisplayContent);
1351 nsContentUtils::DestroyAnonymousContent(&mButtonContent);
1352 nsBlockFrame::DestroyFrom(aDestructRoot);
1355 const nsFrameList&
1356 nsComboboxControlFrame::GetChildList(ChildListID aListID) const
1358 if (kSelectPopupList == aListID) {
1359 return mPopupFrames;
1361 return nsBlockFrame::GetChildList(aListID);
1364 void
1365 nsComboboxControlFrame::GetChildLists(nsTArray<ChildList>* aLists) const
1367 nsBlockFrame::GetChildLists(aLists);
1368 mPopupFrames.AppendIfNonempty(aLists, kSelectPopupList);
1371 void
1372 nsComboboxControlFrame::SetInitialChildList(ChildListID aListID,
1373 nsFrameList& aChildList)
1375 if (kSelectPopupList == aListID) {
1376 mPopupFrames.SetFrames(aChildList);
1377 } else {
1378 for (nsFrameList::Enumerator e(aChildList); !e.AtEnd(); e.Next()) {
1379 nsCOMPtr<nsIFormControl> formControl =
1380 do_QueryInterface(e.get()->GetContent());
1381 if (formControl && formControl->GetType() == NS_FORM_BUTTON_BUTTON) {
1382 mButtonFrame = e.get();
1383 break;
1386 NS_ASSERTION(mButtonFrame, "missing button frame in initial child list");
1387 nsBlockFrame::SetInitialChildList(aListID, aChildList);
1391 //----------------------------------------------------------------------
1392 //nsIRollupListener
1393 //----------------------------------------------------------------------
1394 bool
1395 nsComboboxControlFrame::Rollup(uint32_t aCount, bool aFlush,
1396 const nsIntPoint* pos, nsIContent** aLastRolledUp)
1398 if (!mDroppedDown) {
1399 return false;
1402 bool consume = true;
1403 #ifdef XP_WIN
1404 consume = false;
1405 #endif
1406 nsWeakFrame weakFrame(this);
1407 mListControlFrame->AboutToRollup(); // might destroy us
1408 if (!weakFrame.IsAlive()) {
1409 return consume;
1411 ShowDropDown(false); // might destroy us
1412 if (weakFrame.IsAlive()) {
1413 mListControlFrame->CaptureMouseEvents(false);
1416 if (aFlush && weakFrame.IsAlive()) {
1417 // The popup's visibility doesn't update until the minimize animation has
1418 // finished, so call UpdateWidgetGeometry to update it right away.
1419 nsViewManager* viewManager = mDropdownFrame->GetView()->GetViewManager();
1420 viewManager->UpdateWidgetGeometry();
1423 return consume;
1426 nsIWidget*
1427 nsComboboxControlFrame::GetRollupWidget()
1429 nsView* view = mDropdownFrame->GetView();
1430 MOZ_ASSERT(view);
1431 return view->GetWidget();
1434 void
1435 nsComboboxControlFrame::RollupFromList()
1437 if (ShowList(false))
1438 mListControlFrame->CaptureMouseEvents(false);
1441 int32_t
1442 nsComboboxControlFrame::UpdateRecentIndex(int32_t aIndex)
1444 int32_t index = mRecentSelectedIndex;
1445 if (mRecentSelectedIndex == NS_SKIP_NOTIFY_INDEX || aIndex == NS_SKIP_NOTIFY_INDEX)
1446 mRecentSelectedIndex = aIndex;
1447 return index;
1450 class nsDisplayComboboxFocus : public nsDisplayItem {
1451 public:
1452 nsDisplayComboboxFocus(nsDisplayListBuilder* aBuilder,
1453 nsComboboxControlFrame* aFrame)
1454 : nsDisplayItem(aBuilder, aFrame) {
1455 MOZ_COUNT_CTOR(nsDisplayComboboxFocus);
1457 #ifdef NS_BUILD_REFCNT_LOGGING
1458 virtual ~nsDisplayComboboxFocus() {
1459 MOZ_COUNT_DTOR(nsDisplayComboboxFocus);
1461 #endif
1463 virtual void Paint(nsDisplayListBuilder* aBuilder,
1464 nsRenderingContext* aCtx) MOZ_OVERRIDE;
1465 NS_DISPLAY_DECL_NAME("ComboboxFocus", TYPE_COMBOBOX_FOCUS)
1468 void nsDisplayComboboxFocus::Paint(nsDisplayListBuilder* aBuilder,
1469 nsRenderingContext* aCtx)
1471 static_cast<nsComboboxControlFrame*>(mFrame)
1472 ->PaintFocus(*aCtx, ToReferenceFrame());
1475 void
1476 nsComboboxControlFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
1477 const nsRect& aDirtyRect,
1478 const nsDisplayListSet& aLists)
1480 #ifdef NOISY
1481 printf("%p paint at (%d, %d, %d, %d)\n", this,
1482 aDirtyRect.x, aDirtyRect.y, aDirtyRect.width, aDirtyRect.height);
1483 #endif
1485 if (aBuilder->IsForEventDelivery()) {
1486 // Don't allow children to receive events.
1487 // REVIEW: following old GetFrameForPoint
1488 DisplayBorderBackgroundOutline(aBuilder, aLists);
1489 } else {
1490 // REVIEW: Our in-flow child frames are inline-level so they will paint in our
1491 // content list, so we don't need to mess with layers.
1492 nsBlockFrame::BuildDisplayList(aBuilder, aDirtyRect, aLists);
1495 // draw a focus indicator only when focus rings should be drawn
1496 nsIDocument* doc = mContent->GetComposedDoc();
1497 if (doc) {
1498 nsPIDOMWindow* window = doc->GetWindow();
1499 if (window && window->ShouldShowFocusRing()) {
1500 nsPresContext *presContext = PresContext();
1501 const nsStyleDisplay *disp = StyleDisplay();
1502 if ((!IsThemed(disp) ||
1503 !presContext->GetTheme()->ThemeDrawsFocusForWidget(disp->mAppearance)) &&
1504 mDisplayFrame && IsVisibleForPainting(aBuilder)) {
1505 aLists.Content()->AppendNewToTop(
1506 new (aBuilder) nsDisplayComboboxFocus(aBuilder, this));
1511 DisplaySelectionOverlay(aBuilder, aLists.Content());
1514 void nsComboboxControlFrame::PaintFocus(nsRenderingContext& aRenderingContext,
1515 nsPoint aPt)
1517 /* Do we need to do anything? */
1518 EventStates eventStates = mContent->AsElement()->State();
1519 if (eventStates.HasState(NS_EVENT_STATE_DISABLED) || sFocused != this)
1520 return;
1522 gfxContext* gfx = aRenderingContext.ThebesContext();
1524 gfx->Save();
1525 nsRect clipRect = mDisplayFrame->GetRect() + aPt;
1526 gfx->Clip(NSRectToSnappedRect(clipRect,
1527 PresContext()->AppUnitsPerDevPixel(),
1528 *aRenderingContext.GetDrawTarget()));
1530 // REVIEW: Why does the old code paint mDisplayFrame again? We've
1531 // already painted it in the children above. So clipping it here won't do
1532 // us much good.
1534 /////////////////////
1535 // draw focus
1537 StrokeOptions strokeOptions;
1538 nsLayoutUtils::InitDashPattern(strokeOptions, NS_STYLE_BORDER_STYLE_DOTTED);
1539 ColorPattern color(ToDeviceColor(StyleColor()->mColor));
1540 nscoord onePixel = nsPresContext::CSSPixelsToAppUnits(1);
1541 clipRect.width -= onePixel;
1542 clipRect.height -= onePixel;
1543 Rect r =
1544 ToRect(nsLayoutUtils::RectToGfxRect(clipRect, PresContext()->AppUnitsPerDevPixel()));
1545 StrokeSnappedEdgesOfRect(r, *aRenderingContext.GetDrawTarget(),
1546 color, strokeOptions);
1548 gfx->Restore();
1551 //---------------------------------------------------------
1552 // gets the content (an option) by index and then set it as
1553 // being selected or not selected
1554 //---------------------------------------------------------
1555 NS_IMETHODIMP
1556 nsComboboxControlFrame::OnOptionSelected(int32_t aIndex, bool aSelected)
1558 if (mDroppedDown) {
1559 nsISelectControlFrame *selectFrame = do_QueryFrame(mListControlFrame);
1560 if (selectFrame) {
1561 selectFrame->OnOptionSelected(aIndex, aSelected);
1563 } else {
1564 if (aSelected) {
1565 nsAutoScriptBlocker blocker;
1566 RedisplayText(aIndex);
1567 } else {
1568 nsWeakFrame weakFrame(this);
1569 RedisplaySelectedText();
1570 if (weakFrame.IsAlive()) {
1571 FireValueChangeEvent(); // Fire after old option is unselected
1576 return NS_OK;
1579 void nsComboboxControlFrame::FireValueChangeEvent()
1581 // Fire ValueChange event to indicate data value of combo box has changed
1582 nsContentUtils::AddScriptRunner(
1583 new AsyncEventDispatcher(mContent, NS_LITERAL_STRING("ValueChange"), true,
1584 false));
1587 void
1588 nsComboboxControlFrame::OnContentReset()
1590 if (mListControlFrame) {
1591 mListControlFrame->OnContentReset();
1596 //--------------------------------------------------------
1597 // nsIStatefulFrame
1598 //--------------------------------------------------------
1599 NS_IMETHODIMP
1600 nsComboboxControlFrame::SaveState(nsPresState** aState)
1602 if (!mListControlFrame)
1603 return NS_ERROR_FAILURE;
1605 nsIStatefulFrame* stateful = do_QueryFrame(mListControlFrame);
1606 return stateful->SaveState(aState);
1609 NS_IMETHODIMP
1610 nsComboboxControlFrame::RestoreState(nsPresState* aState)
1612 if (!mListControlFrame)
1613 return NS_ERROR_FAILURE;
1615 nsIStatefulFrame* stateful = do_QueryFrame(mListControlFrame);
1616 NS_ASSERTION(stateful, "Must implement nsIStatefulFrame");
1617 return stateful->RestoreState(aState);
1621 // Fennec uses a custom combobox built-in widget.
1624 /* static */
1625 bool
1626 nsComboboxControlFrame::ToolkitHasNativePopup()
1628 #ifdef MOZ_USE_NATIVE_POPUP_WINDOWS
1629 return true;
1630 #else
1631 return false;
1632 #endif /* MOZ_USE_NATIVE_POPUP_WINDOWS */