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