Line data Source code
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 :
6 : #include "nsComboboxControlFrame.h"
7 :
8 : #include "gfxContext.h"
9 : #include "gfxUtils.h"
10 : #include "mozilla/gfx/2D.h"
11 : #include "mozilla/gfx/PathHelpers.h"
12 : #include "nsCOMPtr.h"
13 : #include "nsFocusManager.h"
14 : #include "nsFormControlFrame.h"
15 : #include "nsGkAtoms.h"
16 : #include "nsCSSAnonBoxes.h"
17 : #include "nsHTMLParts.h"
18 : #include "nsIFormControl.h"
19 : #include "nsNameSpaceManager.h"
20 : #include "nsIListControlFrame.h"
21 : #include "nsPIDOMWindow.h"
22 : #include "nsIPresShell.h"
23 : #include "nsPresState.h"
24 : #include "nsContentList.h"
25 : #include "nsView.h"
26 : #include "nsViewManager.h"
27 : #include "nsIDOMEventListener.h"
28 : #include "nsIDOMNode.h"
29 : #include "nsISelectControlFrame.h"
30 : #include "nsContentUtils.h"
31 : #include "nsIDocument.h"
32 : #include "nsIScrollableFrame.h"
33 : #include "nsListControlFrame.h"
34 : #include "mozilla/StyleSetHandle.h"
35 : #include "mozilla/StyleSetHandleInlines.h"
36 : #include "nsNodeInfoManager.h"
37 : #include "nsContentCreatorFunctions.h"
38 : #include "nsLayoutUtils.h"
39 : #include "nsDisplayList.h"
40 : #include "nsITheme.h"
41 : #include "nsThemeConstants.h"
42 : #include "mozilla/Likely.h"
43 : #include <algorithm>
44 : #include "nsTextNode.h"
45 : #include "mozilla/AsyncEventDispatcher.h"
46 : #include "mozilla/EventStates.h"
47 : #include "mozilla/LookAndFeel.h"
48 : #include "mozilla/MouseEvents.h"
49 : #include "mozilla/Unused.h"
50 : #include "gfx2DGlue.h"
51 : #include "mozilla/widget/nsAutoRollup.h"
52 :
53 : #ifdef XP_WIN
54 : #define COMBOBOX_ROLLUP_CONSUME_EVENT 0
55 : #else
56 : #define COMBOBOX_ROLLUP_CONSUME_EVENT 1
57 : #endif
58 :
59 : using namespace mozilla;
60 : using namespace mozilla::gfx;
61 :
62 : NS_IMETHODIMP
63 0 : nsComboboxControlFrame::RedisplayTextEvent::Run()
64 : {
65 0 : if (mControlFrame)
66 0 : mControlFrame->HandleRedisplayTextEvent();
67 0 : return NS_OK;
68 : }
69 :
70 : class nsPresState;
71 :
72 : #define FIX_FOR_BUG_53259
73 :
74 : // Drop down list event management.
75 : // The combo box uses the following strategy for managing the drop-down list.
76 : // If the combo box or its arrow button is clicked on the drop-down list is displayed
77 : // If mouse exits the combo box with the drop-down list displayed the drop-down list
78 : // is asked to capture events
79 : // The drop-down list will capture all events including mouse down and up and will always
80 : // return with ListWasSelected method call regardless of whether an item in the list was
81 : // actually selected.
82 : // The ListWasSelected code will turn off mouse-capture for the drop-down list.
83 : // The drop-down list does not explicitly set capture when it is in the drop-down mode.
84 :
85 :
86 : /**
87 : * Helper class that listens to the combo boxes button. If the button is pressed the
88 : * combo box is toggled to open or close. this is used by Accessibility which presses
89 : * that button Programmatically.
90 : */
91 : class nsComboButtonListener : public nsIDOMEventListener
92 : {
93 : private:
94 0 : virtual ~nsComboButtonListener() {}
95 :
96 : public:
97 : NS_DECL_ISUPPORTS
98 :
99 0 : NS_IMETHOD HandleEvent(nsIDOMEvent*) override
100 : {
101 0 : mComboBox->ShowDropDown(!mComboBox->IsDroppedDown());
102 0 : return NS_OK;
103 : }
104 :
105 0 : explicit nsComboButtonListener(nsComboboxControlFrame* aCombobox)
106 0 : {
107 0 : mComboBox = aCombobox;
108 0 : }
109 :
110 : nsComboboxControlFrame* mComboBox;
111 : };
112 :
113 0 : NS_IMPL_ISUPPORTS(nsComboButtonListener,
114 : nsIDOMEventListener)
115 :
116 : // static class data member for Bug 32920
117 : nsComboboxControlFrame* nsComboboxControlFrame::sFocused = nullptr;
118 :
119 : nsComboboxControlFrame*
120 0 : NS_NewComboboxControlFrame(nsIPresShell* aPresShell, nsStyleContext* aContext, nsFrameState aStateFlags)
121 : {
122 0 : nsComboboxControlFrame* it = new (aPresShell) nsComboboxControlFrame(aContext);
123 :
124 0 : if (it) {
125 : // set the state flags (if any are provided)
126 0 : it->AddStateBits(aStateFlags);
127 : }
128 :
129 0 : return it;
130 : }
131 :
132 0 : NS_IMPL_FRAMEARENA_HELPERS(nsComboboxControlFrame)
133 :
134 : //-----------------------------------------------------------
135 : // Reflow Debugging Macros
136 : // These let us "see" how many reflow counts are happening
137 : //-----------------------------------------------------------
138 : #ifdef DO_REFLOW_COUNTER
139 :
140 : #define MAX_REFLOW_CNT 1024
141 : static int32_t gTotalReqs = 0;;
142 : static int32_t gTotalReflows = 0;;
143 : static int32_t gReflowControlCntRQ[MAX_REFLOW_CNT];
144 : static int32_t gReflowControlCnt[MAX_REFLOW_CNT];
145 : static int32_t gReflowInx = -1;
146 :
147 : #define REFLOW_COUNTER() \
148 : if (mReflowId > -1) \
149 : gReflowControlCnt[mReflowId]++;
150 :
151 : #define REFLOW_COUNTER_REQUEST() \
152 : if (mReflowId > -1) \
153 : gReflowControlCntRQ[mReflowId]++;
154 :
155 : #define REFLOW_COUNTER_DUMP(__desc) \
156 : if (mReflowId > -1) {\
157 : gTotalReqs += gReflowControlCntRQ[mReflowId];\
158 : gTotalReflows += gReflowControlCnt[mReflowId];\
159 : printf("** Id:%5d %s RF: %d RQ: %d %d/%d %5.2f\n", \
160 : mReflowId, (__desc), \
161 : gReflowControlCnt[mReflowId], \
162 : gReflowControlCntRQ[mReflowId],\
163 : gTotalReflows, gTotalReqs, float(gTotalReflows)/float(gTotalReqs)*100.0f);\
164 : }
165 :
166 : #define REFLOW_COUNTER_INIT() \
167 : if (gReflowInx < MAX_REFLOW_CNT) { \
168 : gReflowInx++; \
169 : mReflowId = gReflowInx; \
170 : gReflowControlCnt[mReflowId] = 0; \
171 : gReflowControlCntRQ[mReflowId] = 0; \
172 : } else { \
173 : mReflowId = -1; \
174 : }
175 :
176 : // reflow messages
177 : #define REFLOW_DEBUG_MSG(_msg1) printf((_msg1))
178 : #define REFLOW_DEBUG_MSG2(_msg1, _msg2) printf((_msg1), (_msg2))
179 : #define REFLOW_DEBUG_MSG3(_msg1, _msg2, _msg3) printf((_msg1), (_msg2), (_msg3))
180 : #define REFLOW_DEBUG_MSG4(_msg1, _msg2, _msg3, _msg4) printf((_msg1), (_msg2), (_msg3), (_msg4))
181 :
182 : #else //-------------
183 :
184 : #define REFLOW_COUNTER_REQUEST()
185 : #define REFLOW_COUNTER()
186 : #define REFLOW_COUNTER_DUMP(__desc)
187 : #define REFLOW_COUNTER_INIT()
188 :
189 : #define REFLOW_DEBUG_MSG(_msg)
190 : #define REFLOW_DEBUG_MSG2(_msg1, _msg2)
191 : #define REFLOW_DEBUG_MSG3(_msg1, _msg2, _msg3)
192 : #define REFLOW_DEBUG_MSG4(_msg1, _msg2, _msg3, _msg4)
193 :
194 :
195 : #endif
196 :
197 : //------------------------------------------
198 : // This is for being VERY noisy
199 : //------------------------------------------
200 : #ifdef DO_VERY_NOISY
201 : #define REFLOW_NOISY_MSG(_msg1) printf((_msg1))
202 : #define REFLOW_NOISY_MSG2(_msg1, _msg2) printf((_msg1), (_msg2))
203 : #define REFLOW_NOISY_MSG3(_msg1, _msg2, _msg3) printf((_msg1), (_msg2), (_msg3))
204 : #define REFLOW_NOISY_MSG4(_msg1, _msg2, _msg3, _msg4) printf((_msg1), (_msg2), (_msg3), (_msg4))
205 : #else
206 : #define REFLOW_NOISY_MSG(_msg)
207 : #define REFLOW_NOISY_MSG2(_msg1, _msg2)
208 : #define REFLOW_NOISY_MSG3(_msg1, _msg2, _msg3)
209 : #define REFLOW_NOISY_MSG4(_msg1, _msg2, _msg3, _msg4)
210 : #endif
211 :
212 : //------------------------------------------
213 : // Displays value in pixels or twips
214 : //------------------------------------------
215 : #ifdef DO_PIXELS
216 : #define PX(__v) __v / 15
217 : #else
218 : #define PX(__v) __v
219 : #endif
220 :
221 : //------------------------------------------------------
222 : //-- Done with macros
223 : //------------------------------------------------------
224 :
225 0 : nsComboboxControlFrame::nsComboboxControlFrame(nsStyleContext* aContext)
226 : : nsBlockFrame(aContext, kClassID)
227 : , mDisplayFrame(nullptr)
228 : , mButtonFrame(nullptr)
229 : , mDropdownFrame(nullptr)
230 : , mListControlFrame(nullptr)
231 : , mDisplayISize(0)
232 : , mRecentSelectedIndex(NS_SKIP_NOTIFY_INDEX)
233 : , mDisplayedIndex(-1)
234 : , mLastDropDownBeforeScreenBCoord(nscoord_MIN)
235 : , mLastDropDownAfterScreenBCoord(nscoord_MIN)
236 : , mDroppedDown(false)
237 : , mInRedisplayText(false)
238 : , mDelayedShowDropDown(false)
239 0 : , mIsOpenInParentProcess(false)
240 : {
241 : REFLOW_COUNTER_INIT()
242 0 : }
243 :
244 : //--------------------------------------------------------------
245 0 : nsComboboxControlFrame::~nsComboboxControlFrame()
246 : {
247 : REFLOW_COUNTER_DUMP("nsCCF");
248 0 : }
249 :
250 : //--------------------------------------------------------------
251 :
252 0 : NS_QUERYFRAME_HEAD(nsComboboxControlFrame)
253 0 : NS_QUERYFRAME_ENTRY(nsComboboxControlFrame)
254 0 : NS_QUERYFRAME_ENTRY(nsIComboboxControlFrame)
255 0 : NS_QUERYFRAME_ENTRY(nsIFormControlFrame)
256 0 : NS_QUERYFRAME_ENTRY(nsIAnonymousContentCreator)
257 0 : NS_QUERYFRAME_ENTRY(nsISelectControlFrame)
258 0 : NS_QUERYFRAME_ENTRY(nsIStatefulFrame)
259 0 : NS_QUERYFRAME_TAIL_INHERITING(nsBlockFrame)
260 :
261 : #ifdef ACCESSIBILITY
262 : a11y::AccType
263 0 : nsComboboxControlFrame::AccessibleType()
264 : {
265 0 : return a11y::eHTMLComboboxType;
266 : }
267 : #endif
268 :
269 : void
270 0 : nsComboboxControlFrame::SetFocus(bool aOn, bool aRepaint)
271 : {
272 0 : AutoWeakFrame weakFrame(this);
273 0 : if (aOn) {
274 0 : nsListControlFrame::ComboboxFocusSet();
275 0 : sFocused = this;
276 0 : if (mDelayedShowDropDown) {
277 0 : ShowDropDown(true); // might destroy us
278 0 : if (!weakFrame.IsAlive()) {
279 0 : return;
280 : }
281 : }
282 : } else {
283 0 : sFocused = nullptr;
284 0 : mDelayedShowDropDown = false;
285 0 : if (mDroppedDown) {
286 0 : mListControlFrame->ComboboxFinish(mDisplayedIndex); // might destroy us
287 0 : if (!weakFrame.IsAlive()) {
288 0 : return;
289 : }
290 : }
291 : // May delete |this|.
292 0 : mListControlFrame->FireOnInputAndOnChange();
293 : }
294 :
295 0 : if (!weakFrame.IsAlive()) {
296 0 : return;
297 : }
298 :
299 : // This is needed on a temporary basis. It causes the focus
300 : // rect to be drawn. This is much faster than ReResolvingStyle
301 : // Bug 32920
302 0 : InvalidateFrame();
303 : }
304 :
305 : void
306 0 : nsComboboxControlFrame::ShowPopup(bool aShowPopup)
307 : {
308 0 : nsView* view = mDropdownFrame->GetView();
309 0 : nsViewManager* viewManager = view->GetViewManager();
310 :
311 0 : if (aShowPopup) {
312 0 : nsRect rect = mDropdownFrame->GetRect();
313 0 : rect.x = rect.y = 0;
314 0 : viewManager->ResizeView(view, rect);
315 0 : viewManager->SetViewVisibility(view, nsViewVisibility_kShow);
316 : } else {
317 0 : viewManager->SetViewVisibility(view, nsViewVisibility_kHide);
318 0 : nsRect emptyRect(0, 0, 0, 0);
319 0 : viewManager->ResizeView(view, emptyRect);
320 : }
321 :
322 : // fire a popup dom event if it is safe to do so
323 0 : nsCOMPtr<nsIPresShell> shell = PresContext()->GetPresShell();
324 0 : if (shell && nsContentUtils::IsSafeToRunScript()) {
325 0 : nsEventStatus status = nsEventStatus_eIgnore;
326 : WidgetMouseEvent event(true, aShowPopup ? eXULPopupShowing : eXULPopupHiding,
327 0 : nullptr, WidgetMouseEvent::eReal);
328 :
329 0 : shell->HandleDOMEventWithTarget(mContent, &event, &status);
330 : }
331 0 : }
332 :
333 : bool
334 0 : nsComboboxControlFrame::ShowList(bool aShowList)
335 : {
336 0 : nsView* view = mDropdownFrame->GetView();
337 0 : if (aShowList) {
338 0 : NS_ASSERTION(!view->HasWidget(),
339 : "We shouldn't have a widget before we need to display the popup");
340 :
341 : // Create the widget for the drop-down list
342 0 : view->GetViewManager()->SetViewFloating(view, true);
343 :
344 0 : nsWidgetInitData widgetData;
345 0 : widgetData.mWindowType = eWindowType_popup;
346 0 : widgetData.mBorderStyle = eBorderStyle_default;
347 0 : view->CreateWidgetForPopup(&widgetData);
348 : } else {
349 0 : nsIWidget* widget = view->GetWidget();
350 0 : if (widget) {
351 : // We must do this before ShowPopup in case it destroys us (bug 813442).
352 0 : widget->CaptureRollupEvents(this, false);
353 : }
354 : }
355 :
356 0 : AutoWeakFrame weakFrame(this);
357 0 : ShowPopup(aShowList); // might destroy us
358 0 : if (!weakFrame.IsAlive()) {
359 0 : return false;
360 : }
361 :
362 0 : mDroppedDown = aShowList;
363 0 : nsIWidget* widget = view->GetWidget();
364 0 : if (mDroppedDown) {
365 : // The listcontrol frame will call back to the nsComboboxControlFrame's
366 : // ListWasSelected which will stop the capture.
367 0 : mListControlFrame->AboutToDropDown();
368 0 : mListControlFrame->CaptureMouseEvents(true);
369 0 : if (widget) {
370 0 : widget->CaptureRollupEvents(this, true);
371 : }
372 : } else {
373 0 : if (widget) {
374 0 : view->DestroyWidget();
375 : }
376 : }
377 :
378 0 : return weakFrame.IsAlive();
379 : }
380 :
381 : class nsResizeDropdownAtFinalPosition final
382 : : public nsIReflowCallback, public Runnable
383 : {
384 : public:
385 0 : explicit nsResizeDropdownAtFinalPosition(nsComboboxControlFrame* aFrame)
386 0 : : mozilla::Runnable("nsResizeDropdownAtFinalPosition")
387 0 : , mFrame(aFrame)
388 : {
389 0 : }
390 :
391 : protected:
392 0 : ~nsResizeDropdownAtFinalPosition()
393 0 : {
394 0 : }
395 :
396 : public:
397 0 : virtual bool ReflowFinished() override
398 : {
399 0 : Run();
400 0 : NS_RELEASE_THIS();
401 0 : return false;
402 : }
403 :
404 0 : virtual void ReflowCallbackCanceled() override
405 : {
406 0 : NS_RELEASE_THIS();
407 0 : }
408 :
409 0 : NS_IMETHOD Run() override
410 : {
411 0 : if (mFrame.IsAlive()) {
412 0 : static_cast<nsComboboxControlFrame*>(mFrame.GetFrame())->
413 0 : AbsolutelyPositionDropDown();
414 : }
415 0 : return NS_OK;
416 : }
417 :
418 : WeakFrame mFrame;
419 : };
420 :
421 : void
422 0 : nsComboboxControlFrame::ReflowDropdown(nsPresContext* aPresContext,
423 : const ReflowInput& aReflowInput)
424 : {
425 : // All we want out of it later on, really, is the block size of a row, so we
426 : // don't even need to cache mDropdownFrame's ascent or anything. If we don't
427 : // need to reflow it, just bail out here.
428 0 : if (!aReflowInput.ShouldReflowAllKids() &&
429 0 : !NS_SUBTREE_DIRTY(mDropdownFrame)) {
430 0 : return;
431 : }
432 :
433 : // XXXbz this will, for small-block-size dropdowns, have extra space
434 : // on the appropriate edge for the scrollbar we don't show... but
435 : // that's the best we can do here for now.
436 0 : WritingMode wm = mDropdownFrame->GetWritingMode();
437 0 : LogicalSize availSize = aReflowInput.AvailableSize(wm);
438 0 : availSize.BSize(wm) = NS_UNCONSTRAINEDSIZE;
439 : ReflowInput kidReflowInput(aPresContext, aReflowInput, mDropdownFrame,
440 0 : availSize);
441 :
442 : // If the dropdown's intrinsic inline size is narrower than our
443 : // specified inline size, then expand it out. We want our border-box
444 : // inline size to end up the same as the dropdown's so account for
445 : // both sets of mComputedBorderPadding.
446 0 : nscoord forcedISize = aReflowInput.ComputedISize() +
447 0 : aReflowInput.ComputedLogicalBorderPadding().IStartEnd(wm) -
448 0 : kidReflowInput.ComputedLogicalBorderPadding().IStartEnd(wm);
449 0 : kidReflowInput.SetComputedISize(std::max(kidReflowInput.ComputedISize(),
450 0 : forcedISize));
451 :
452 : // ensure we start off hidden
453 0 : if (!mDroppedDown && GetStateBits() & NS_FRAME_FIRST_REFLOW) {
454 0 : nsView* view = mDropdownFrame->GetView();
455 0 : nsViewManager* viewManager = view->GetViewManager();
456 0 : viewManager->SetViewVisibility(view, nsViewVisibility_kHide);
457 0 : nsRect emptyRect(0, 0, 0, 0);
458 0 : viewManager->ResizeView(view, emptyRect);
459 : }
460 :
461 : // Allow the child to move/size/change-visibility its view if it's currently
462 : // dropped down
463 0 : int32_t flags = mDroppedDown ? 0
464 : : NS_FRAME_NO_MOVE_FRAME |
465 : NS_FRAME_NO_VISIBILITY |
466 0 : NS_FRAME_NO_SIZE_VIEW;
467 :
468 : //XXX Can this be different from the dropdown's writing mode?
469 : // That would be odd!
470 : // Note that we don't need to pass the true frame position or container size
471 : // to ReflowChild or FinishReflowChild here; it will be positioned as needed
472 : // by AbsolutelyPositionDropDown().
473 0 : WritingMode outerWM = GetWritingMode();
474 0 : const nsSize dummyContainerSize;
475 0 : ReflowOutput desiredSize(aReflowInput);
476 0 : nsReflowStatus ignoredStatus;
477 0 : ReflowChild(mDropdownFrame, aPresContext, desiredSize,
478 0 : kidReflowInput, outerWM, LogicalPoint(outerWM),
479 0 : dummyContainerSize, flags, ignoredStatus);
480 :
481 : // Set the child's width and height to its desired size
482 0 : FinishReflowChild(mDropdownFrame, aPresContext, desiredSize, &kidReflowInput,
483 0 : outerWM, LogicalPoint(outerWM), dummyContainerSize, flags);
484 : }
485 :
486 : nsPoint
487 0 : nsComboboxControlFrame::GetCSSTransformTranslation()
488 : {
489 0 : nsIFrame* frame = this;
490 0 : bool is3DTransform = false;
491 0 : Matrix transform;
492 0 : while (frame) {
493 : nsIFrame* parent;
494 0 : Matrix4x4 ctm = frame->GetTransformMatrix(nullptr, &parent);
495 0 : Matrix matrix;
496 0 : if (ctm.Is2D(&matrix)) {
497 0 : transform = transform * matrix;
498 : } else {
499 0 : is3DTransform = true;
500 0 : break;
501 : }
502 0 : frame = parent;
503 : }
504 0 : nsPoint translation;
505 0 : if (!is3DTransform && !transform.HasNonTranslation()) {
506 0 : nsPresContext* pc = PresContext();
507 : // To get the translation introduced only by transforms we subtract the
508 : // regular non-transform translation.
509 0 : nsRootPresContext* rootPC = pc->GetRootPresContext();
510 0 : if (rootPC) {
511 0 : int32_t apd = pc->AppUnitsPerDevPixel();
512 0 : translation.x = NSFloatPixelsToAppUnits(transform._31, apd);
513 0 : translation.y = NSFloatPixelsToAppUnits(transform._32, apd);
514 0 : translation -= GetOffsetToCrossDoc(rootPC->PresShell()->GetRootFrame());
515 : }
516 : }
517 0 : return translation;
518 : }
519 :
520 0 : class nsAsyncRollup : public Runnable
521 : {
522 : public:
523 0 : explicit nsAsyncRollup(nsComboboxControlFrame* aFrame)
524 0 : : mozilla::Runnable("nsAsyncRollup")
525 0 : , mFrame(aFrame)
526 : {
527 0 : }
528 0 : NS_IMETHOD Run() override
529 : {
530 0 : if (mFrame.IsAlive()) {
531 0 : static_cast<nsComboboxControlFrame*>(mFrame.GetFrame())
532 0 : ->RollupFromList();
533 : }
534 0 : return NS_OK;
535 : }
536 : WeakFrame mFrame;
537 : };
538 :
539 0 : class nsAsyncResize : public Runnable
540 : {
541 : public:
542 0 : explicit nsAsyncResize(nsComboboxControlFrame* aFrame)
543 0 : : mozilla::Runnable("nsAsyncResize")
544 0 : , mFrame(aFrame)
545 : {
546 0 : }
547 0 : NS_IMETHOD Run() override
548 : {
549 0 : if (mFrame.IsAlive()) {
550 : nsComboboxControlFrame* combo =
551 0 : static_cast<nsComboboxControlFrame*>(mFrame.GetFrame());
552 0 : static_cast<nsListControlFrame*>(combo->mDropdownFrame)->
553 0 : SetSuppressScrollbarUpdate(true);
554 0 : nsCOMPtr<nsIPresShell> shell = mFrame->PresContext()->PresShell();
555 0 : shell->FrameNeedsReflow(combo->mDropdownFrame, nsIPresShell::eResize,
556 0 : NS_FRAME_IS_DIRTY);
557 0 : shell->FlushPendingNotifications(FlushType::Layout);
558 0 : if (mFrame.IsAlive()) {
559 0 : combo = static_cast<nsComboboxControlFrame*>(mFrame.GetFrame());
560 0 : static_cast<nsListControlFrame*>(combo->mDropdownFrame)->
561 0 : SetSuppressScrollbarUpdate(false);
562 0 : if (combo->mDelayedShowDropDown) {
563 0 : combo->ShowDropDown(true);
564 : }
565 : }
566 : }
567 0 : return NS_OK;
568 : }
569 : WeakFrame mFrame;
570 : };
571 :
572 : void
573 0 : nsComboboxControlFrame::GetAvailableDropdownSpace(WritingMode aWM,
574 : nscoord* aBefore,
575 : nscoord* aAfter,
576 : LogicalPoint* aTranslation)
577 : {
578 0 : MOZ_ASSERT(!XRE_IsContentProcess());
579 : // Note: At first glance, it appears that you could simply get the
580 : // absolute bounding box for the dropdown list by first getting its
581 : // view, then getting the view's nsIWidget, then asking the nsIWidget
582 : // for its AbsoluteBounds.
583 : // The problem with this approach, is that the dropdown list's bcoord
584 : // location can change based on whether the dropdown is placed after
585 : // or before the display frame. The approach taken here is to get the
586 : // absolute position of the display frame and use its location to
587 : // determine if the dropdown will go offscreen.
588 :
589 : // Normal frame geometry (eg GetOffsetTo, mRect) doesn't include transforms.
590 : // In the special case that our transform is only a 2D translation we
591 : // introduce this hack so that the dropdown will show up in the right place.
592 : // Use null container size when converting a vector from logical to physical.
593 0 : const nsSize nullContainerSize;
594 0 : *aTranslation = LogicalPoint(aWM, GetCSSTransformTranslation(),
595 : nullContainerSize);
596 0 : *aBefore = 0;
597 0 : *aAfter = 0;
598 :
599 0 : nsRect screen = nsFormControlFrame::GetUsableScreenRect(PresContext());
600 0 : nsSize containerSize = screen.Size();
601 0 : LogicalRect logicalScreen(aWM, screen, containerSize);
602 0 : if (mLastDropDownAfterScreenBCoord == nscoord_MIN) {
603 0 : LogicalRect thisScreenRect(aWM, GetScreenRectInAppUnits(),
604 0 : containerSize);
605 0 : mLastDropDownAfterScreenBCoord = thisScreenRect.BEnd(aWM) +
606 0 : aTranslation->B(aWM);
607 0 : mLastDropDownBeforeScreenBCoord = thisScreenRect.BStart(aWM) +
608 0 : aTranslation->B(aWM);
609 : }
610 :
611 : nscoord minBCoord;
612 0 : nsPresContext* pc = PresContext()->GetToplevelContentDocumentPresContext();
613 0 : nsIFrame* root = pc ? pc->PresShell()->GetRootFrame() : nullptr;
614 0 : if (root) {
615 0 : minBCoord = LogicalRect(aWM,
616 0 : root->GetScreenRectInAppUnits(),
617 0 : containerSize).BStart(aWM);
618 0 : if (mLastDropDownAfterScreenBCoord < minBCoord) {
619 : // Don't allow the drop-down to be placed before the content area.
620 0 : return;
621 : }
622 : } else {
623 0 : minBCoord = logicalScreen.BStart(aWM);
624 : }
625 :
626 0 : nscoord after = logicalScreen.BEnd(aWM) - mLastDropDownAfterScreenBCoord;
627 0 : nscoord before = mLastDropDownBeforeScreenBCoord - minBCoord;
628 :
629 : // If the difference between the space before and after is less
630 : // than a row-block-size, then we favor the space after.
631 0 : if (before >= after) {
632 0 : nsListControlFrame* lcf = static_cast<nsListControlFrame*>(mDropdownFrame);
633 0 : nscoord rowBSize = lcf->GetBSizeOfARow();
634 0 : if (before < after + rowBSize) {
635 0 : before -= rowBSize;
636 : }
637 : }
638 :
639 0 : *aAfter = after;
640 0 : *aBefore = before;
641 : }
642 :
643 : nsComboboxControlFrame::DropDownPositionState
644 0 : nsComboboxControlFrame::AbsolutelyPositionDropDown()
645 : {
646 0 : if (XRE_IsContentProcess()) {
647 0 : return eDropDownPositionSuppressed;
648 : }
649 :
650 0 : WritingMode wm = GetWritingMode();
651 0 : LogicalPoint translation(wm);
652 : nscoord before, after;
653 0 : mLastDropDownAfterScreenBCoord = nscoord_MIN;
654 0 : GetAvailableDropdownSpace(wm, &before, &after, &translation);
655 0 : if (before <= 0 && after <= 0) {
656 0 : if (IsDroppedDown()) {
657 : // Hide the view immediately to minimize flicker.
658 0 : nsView* view = mDropdownFrame->GetView();
659 0 : view->GetViewManager()->SetViewVisibility(view, nsViewVisibility_kHide);
660 0 : NS_DispatchToCurrentThread(new nsAsyncRollup(this));
661 : }
662 0 : return eDropDownPositionSuppressed;
663 : }
664 :
665 0 : LogicalSize dropdownSize = mDropdownFrame->GetLogicalSize(wm);
666 0 : nscoord bSize = std::max(before, after);
667 0 : nsListControlFrame* lcf = static_cast<nsListControlFrame*>(mDropdownFrame);
668 0 : if (bSize < dropdownSize.BSize(wm)) {
669 0 : if (lcf->GetNumDisplayRows() > 1) {
670 : // The drop-down doesn't fit and currently shows more than 1 row -
671 : // schedule a resize to show fewer rows.
672 0 : NS_DispatchToCurrentThread(new nsAsyncResize(this));
673 0 : return eDropDownPositionPendingResize;
674 : }
675 0 : } else if (bSize > (dropdownSize.BSize(wm) + lcf->GetBSizeOfARow() * 1.5) &&
676 0 : lcf->GetDropdownCanGrow()) {
677 : // The drop-down fits but there is room for at least 1.5 more rows -
678 : // schedule a resize to show more rows if it has more rows to show.
679 : // (1.5 rows for good measure to avoid any rounding issues that would
680 : // lead to a loop of reflow requests)
681 0 : NS_DispatchToCurrentThread(new nsAsyncResize(this));
682 0 : return eDropDownPositionPendingResize;
683 : }
684 :
685 : // Position the drop-down after if there is room, otherwise place it before
686 : // if there is room. If there is no room for it on either side then place
687 : // it after (to avoid overlapping UI like the URL bar).
688 0 : bool b = dropdownSize.BSize(wm)<= after || dropdownSize.BSize(wm) > before;
689 0 : LogicalPoint dropdownPosition(wm, 0, b ? BSize(wm) : -dropdownSize.BSize(wm));
690 :
691 : // Don't position the view unless the position changed since it might cause
692 : // a call to NotifyGeometryChange() and an infinite loop here.
693 0 : nsSize containerSize = GetSize();
694 : const LogicalPoint currentPos =
695 0 : mDropdownFrame->GetLogicalPosition(containerSize);
696 0 : const LogicalPoint newPos = dropdownPosition + translation;
697 0 : if (currentPos != newPos) {
698 0 : mDropdownFrame->SetPosition(wm, newPos, containerSize);
699 0 : nsContainerFrame::PositionFrameView(mDropdownFrame);
700 : }
701 0 : return eDropDownPositionFinal;
702 : }
703 :
704 : void
705 0 : nsComboboxControlFrame::NotifyGeometryChange()
706 : {
707 0 : if (XRE_IsContentProcess()) {
708 0 : return;
709 : }
710 :
711 : // We don't need to resize if we're not dropped down since ShowDropDown
712 : // does that, or if we're dirty then the reflow callback does it,
713 : // or if we have a delayed ShowDropDown pending.
714 0 : if (IsDroppedDown() &&
715 0 : !(GetStateBits() & NS_FRAME_IS_DIRTY) &&
716 0 : !mDelayedShowDropDown) {
717 : // Async because we're likely in a middle of a scroll here so
718 : // frame/view positions are in flux.
719 : RefPtr<nsResizeDropdownAtFinalPosition> resize =
720 0 : new nsResizeDropdownAtFinalPosition(this);
721 0 : NS_DispatchToCurrentThread(resize);
722 : }
723 : }
724 :
725 : //----------------------------------------------------------
726 : //
727 : //----------------------------------------------------------
728 : #ifdef DO_REFLOW_DEBUG
729 : static int myCounter = 0;
730 :
731 : static void printSize(char * aDesc, nscoord aSize)
732 : {
733 : printf(" %s: ", aDesc);
734 : if (aSize == NS_UNCONSTRAINEDSIZE) {
735 : printf("UC");
736 : } else {
737 : printf("%d", PX(aSize));
738 : }
739 : }
740 : #endif
741 :
742 : //-------------------------------------------------------------------
743 : //-- Main Reflow for the Combobox
744 : //-------------------------------------------------------------------
745 :
746 : nscoord
747 0 : nsComboboxControlFrame::GetIntrinsicISize(gfxContext* aRenderingContext,
748 : nsLayoutUtils::IntrinsicISizeType aType)
749 : {
750 : // get the scrollbar width, we'll use this later
751 0 : nscoord scrollbarWidth = 0;
752 0 : nsPresContext* presContext = PresContext();
753 0 : if (mListControlFrame) {
754 0 : nsIScrollableFrame* scrollable = do_QueryFrame(mListControlFrame);
755 0 : NS_ASSERTION(scrollable, "List must be a scrollable frame");
756 0 : scrollbarWidth = scrollable->GetNondisappearingScrollbarWidth(
757 0 : presContext, aRenderingContext, GetWritingMode());
758 : }
759 :
760 0 : nscoord displayISize = 0;
761 0 : if (MOZ_LIKELY(mDisplayFrame)) {
762 0 : displayISize = nsLayoutUtils::IntrinsicForContainer(aRenderingContext,
763 0 : mDisplayFrame,
764 : aType);
765 : }
766 :
767 0 : if (mDropdownFrame) {
768 : nscoord dropdownContentISize;
769 : bool isUsingOverlayScrollbars =
770 0 : LookAndFeel::GetInt(LookAndFeel::eIntID_UseOverlayScrollbars) != 0;
771 0 : if (aType == nsLayoutUtils::MIN_ISIZE) {
772 0 : dropdownContentISize = mDropdownFrame->GetMinISize(aRenderingContext);
773 0 : if (isUsingOverlayScrollbars) {
774 0 : dropdownContentISize += scrollbarWidth;
775 : }
776 : } else {
777 0 : NS_ASSERTION(aType == nsLayoutUtils::PREF_ISIZE, "Unexpected type");
778 0 : dropdownContentISize = mDropdownFrame->GetPrefISize(aRenderingContext);
779 0 : if (isUsingOverlayScrollbars) {
780 0 : dropdownContentISize += scrollbarWidth;
781 : }
782 : }
783 0 : dropdownContentISize = NSCoordSaturatingSubtract(dropdownContentISize,
784 : scrollbarWidth,
785 : nscoord_MAX);
786 :
787 0 : displayISize = std::max(dropdownContentISize, displayISize);
788 : }
789 :
790 : // add room for the dropmarker button if there is one
791 0 : const nsStyleDisplay* disp = StyleDisplay();
792 0 : if ((!IsThemed(disp) ||
793 0 : presContext->GetTheme()->ThemeNeedsComboboxDropmarker()) &&
794 0 : disp->mAppearance != NS_THEME_NONE) {
795 0 : displayISize += scrollbarWidth;
796 : }
797 :
798 0 : return displayISize;
799 :
800 : }
801 :
802 : nscoord
803 0 : nsComboboxControlFrame::GetMinISize(gfxContext *aRenderingContext)
804 : {
805 : nscoord minISize;
806 0 : DISPLAY_MIN_WIDTH(this, minISize);
807 0 : minISize = GetIntrinsicISize(aRenderingContext, nsLayoutUtils::MIN_ISIZE);
808 0 : return minISize;
809 : }
810 :
811 : nscoord
812 0 : nsComboboxControlFrame::GetPrefISize(gfxContext *aRenderingContext)
813 : {
814 : nscoord prefISize;
815 0 : DISPLAY_PREF_WIDTH(this, prefISize);
816 0 : prefISize = GetIntrinsicISize(aRenderingContext, nsLayoutUtils::PREF_ISIZE);
817 0 : return prefISize;
818 : }
819 :
820 : void
821 0 : nsComboboxControlFrame::Reflow(nsPresContext* aPresContext,
822 : ReflowOutput& aDesiredSize,
823 : const ReflowInput& aReflowInput,
824 : nsReflowStatus& aStatus)
825 : {
826 0 : MarkInReflow();
827 : // Constraints we try to satisfy:
828 :
829 : // 1) Default inline size of button is the vertical scrollbar size
830 : // 2) If the inline size of button is bigger than our inline size, set
831 : // inline size of button to 0.
832 : // 3) Default block size of button is block size of display area
833 : // 4) Inline size of display area is whatever is left over from our
834 : // inline size after allocating inline size for the button.
835 : // 5) Block Size of display area is GetBSizeOfARow() on the
836 : // mListControlFrame.
837 :
838 0 : if (!mDisplayFrame || !mButtonFrame || !mDropdownFrame) {
839 0 : NS_ERROR("Why did the frame constructor allow this to happen? Fix it!!");
840 0 : return;
841 : }
842 :
843 : // Make sure the displayed text is the same as the selected option, bug 297389.
844 0 : if (!mDroppedDown) {
845 0 : mDisplayedIndex = mListControlFrame->GetSelectedIndex();
846 : }
847 : // In dropped down mode the "selected index" is the hovered menu item,
848 : // we want the last selected item which is |mDisplayedIndex| in this case.
849 0 : RedisplayText();
850 :
851 : // First reflow our dropdown so that we know how tall we should be.
852 0 : ReflowDropdown(aPresContext, aReflowInput);
853 : RefPtr<nsResizeDropdownAtFinalPosition> resize =
854 0 : new nsResizeDropdownAtFinalPosition(this);
855 0 : if (NS_SUCCEEDED(aPresContext->PresShell()->PostReflowCallback(resize))) {
856 : // The reflow callback queue doesn't AddRef so we keep it alive until
857 : // it's released in its ReflowFinished / ReflowCallbackCanceled.
858 0 : Unused << resize.forget();
859 : }
860 :
861 : // Get the width of the vertical scrollbar. That will be the inline
862 : // size of the dropdown button.
863 0 : WritingMode wm = aReflowInput.GetWritingMode();
864 : nscoord buttonISize;
865 0 : const nsStyleDisplay *disp = StyleDisplay();
866 0 : if ((IsThemed(disp) && !aPresContext->GetTheme()->ThemeNeedsComboboxDropmarker()) ||
867 0 : StyleDisplay()->mAppearance == NS_THEME_NONE) {
868 0 : buttonISize = 0;
869 : }
870 : else {
871 0 : nsIScrollableFrame* scrollable = do_QueryFrame(mListControlFrame);
872 0 : NS_ASSERTION(scrollable, "List must be a scrollable frame");
873 0 : buttonISize = scrollable->GetNondisappearingScrollbarWidth(
874 0 : PresContext(), aReflowInput.mRenderingContext, wm);
875 0 : if (buttonISize > aReflowInput.ComputedISize()) {
876 0 : buttonISize = 0;
877 : }
878 : }
879 :
880 0 : mDisplayISize = aReflowInput.ComputedISize() - buttonISize;
881 :
882 0 : nsBlockFrame::Reflow(aPresContext, aDesiredSize, aReflowInput, aStatus);
883 :
884 : // The button should occupy the same space as a scrollbar
885 0 : nsSize containerSize = aDesiredSize.PhysicalSize();
886 0 : LogicalRect buttonRect = mButtonFrame->GetLogicalRect(containerSize);
887 :
888 0 : buttonRect.IStart(wm) =
889 0 : aReflowInput.ComputedLogicalBorderPadding().IStartEnd(wm) +
890 0 : mDisplayISize -
891 0 : (aReflowInput.ComputedLogicalBorderPadding().IEnd(wm) -
892 0 : aReflowInput.ComputedLogicalPadding().IEnd(wm));
893 0 : buttonRect.ISize(wm) = buttonISize;
894 :
895 0 : buttonRect.BStart(wm) = this->GetLogicalUsedBorder(wm).BStart(wm);
896 0 : buttonRect.BSize(wm) = mDisplayFrame->BSize(wm) +
897 0 : this->GetLogicalUsedPadding(wm).BStartEnd(wm);
898 :
899 0 : mButtonFrame->SetRect(buttonRect, containerSize);
900 :
901 0 : if (!aStatus.IsInlineBreakBefore() &&
902 0 : !aStatus.IsFullyComplete()) {
903 : // This frame didn't fit inside a fragmentation container. Splitting
904 : // a nsComboboxControlFrame makes no sense, so we override the status here.
905 0 : aStatus.Reset();
906 : }
907 : }
908 :
909 : //--------------------------------------------------------------
910 :
911 : #ifdef DEBUG_FRAME_DUMP
912 : nsresult
913 0 : nsComboboxControlFrame::GetFrameName(nsAString& aResult) const
914 : {
915 0 : return MakeFrameName(NS_LITERAL_STRING("ComboboxControl"), aResult);
916 : }
917 : #endif
918 :
919 :
920 : //----------------------------------------------------------------------
921 : // nsIComboboxControlFrame
922 : //----------------------------------------------------------------------
923 : void
924 0 : nsComboboxControlFrame::ShowDropDown(bool aDoDropDown)
925 : {
926 0 : MOZ_ASSERT(!XRE_IsContentProcess());
927 0 : mDelayedShowDropDown = false;
928 0 : EventStates eventStates = mContent->AsElement()->State();
929 0 : if (aDoDropDown && eventStates.HasState(NS_EVENT_STATE_DISABLED)) {
930 0 : return;
931 : }
932 :
933 0 : if (!mDroppedDown && aDoDropDown) {
934 0 : nsFocusManager* fm = nsFocusManager::GetFocusManager();
935 0 : if (!fm || fm->GetFocusedContent() == GetContent()) {
936 0 : DropDownPositionState state = AbsolutelyPositionDropDown();
937 0 : if (state == eDropDownPositionFinal) {
938 0 : ShowList(aDoDropDown); // might destroy us
939 0 : } else if (state == eDropDownPositionPendingResize) {
940 : // Delay until after the resize reflow, see nsAsyncResize.
941 0 : mDelayedShowDropDown = true;
942 : }
943 : } else {
944 : // Delay until we get focus, see SetFocus().
945 0 : mDelayedShowDropDown = true;
946 0 : }
947 0 : } else if (mDroppedDown && !aDoDropDown) {
948 0 : ShowList(aDoDropDown); // might destroy us
949 : }
950 : }
951 :
952 : void
953 0 : nsComboboxControlFrame::SetDropDown(nsIFrame* aDropDownFrame)
954 : {
955 0 : mDropdownFrame = aDropDownFrame;
956 0 : mListControlFrame = do_QueryFrame(mDropdownFrame);
957 0 : if (!sFocused && nsContentUtils::IsFocusedContent(GetContent())) {
958 0 : sFocused = this;
959 0 : nsListControlFrame::ComboboxFocusSet();
960 : }
961 0 : }
962 :
963 : nsIFrame*
964 0 : nsComboboxControlFrame::GetDropDown()
965 : {
966 0 : return mDropdownFrame;
967 : }
968 :
969 : ///////////////////////////////////////////////////////////////
970 :
971 : void
972 0 : nsComboboxControlFrame::SetPreviewText(const nsAString& aValue)
973 : {
974 0 : nsAutoString previewValue(aValue);
975 0 : nsContentUtils::RemoveNewlines(previewValue);
976 :
977 0 : mPreviewText = previewValue;
978 0 : RedisplayText();
979 0 : }
980 :
981 : NS_IMETHODIMP
982 0 : nsComboboxControlFrame::RedisplaySelectedText()
983 : {
984 0 : nsAutoScriptBlocker scriptBlocker;
985 0 : mDisplayedIndex = mListControlFrame->GetSelectedIndex();
986 0 : return RedisplayText();
987 : }
988 :
989 :
990 : nsresult
991 0 : nsComboboxControlFrame::RedisplayText()
992 : {
993 0 : nsString previousText(mDisplayedOptionTextOrPreview);
994 : // Get the text to display
995 0 : if (!mPreviewText.IsEmpty()) {
996 0 : mDisplayedOptionTextOrPreview = mPreviewText;
997 0 : } else if (mDisplayedIndex != -1) {
998 0 : mListControlFrame->GetOptionText(mDisplayedIndex, mDisplayedOptionTextOrPreview);
999 : } else {
1000 0 : mDisplayedOptionTextOrPreview.Truncate();
1001 : }
1002 :
1003 : REFLOW_DEBUG_MSG2("RedisplayText \"%s\"\n",
1004 : NS_LossyConvertUTF16toASCII(mDisplayedOptionTextOrPreview).get());
1005 :
1006 : // Send reflow command because the new text maybe larger
1007 0 : nsresult rv = NS_OK;
1008 0 : if (mDisplayContent &&
1009 0 : !previousText.Equals(mDisplayedOptionTextOrPreview)) {
1010 : // Don't call ActuallyDisplayText(true) directly here since that
1011 : // could cause recursive frame construction. See bug 283117 and the comment in
1012 : // HandleRedisplayTextEvent() below.
1013 :
1014 : // Revoke outstanding events to avoid out-of-order events which could mean
1015 : // displaying the wrong text.
1016 0 : mRedisplayTextEvent.Revoke();
1017 :
1018 0 : NS_ASSERTION(!nsContentUtils::IsSafeToRunScript(),
1019 : "If we happen to run our redisplay event now, we might kill "
1020 : "ourselves!");
1021 :
1022 0 : RefPtr<RedisplayTextEvent> event = new RedisplayTextEvent(this);
1023 0 : mRedisplayTextEvent = event;
1024 0 : nsContentUtils::AddScriptRunner(event);
1025 : }
1026 0 : return rv;
1027 : }
1028 :
1029 : void
1030 0 : nsComboboxControlFrame::HandleRedisplayTextEvent()
1031 : {
1032 : // First, make sure that the content model is up to date and we've
1033 : // constructed the frames for all our content in the right places.
1034 : // Otherwise they'll end up under the wrong insertion frame when we
1035 : // ActuallyDisplayText, since that flushes out the content sink by
1036 : // calling SetText on a DOM node with aNotify set to true. See bug
1037 : // 289730.
1038 0 : AutoWeakFrame weakThis(this);
1039 0 : PresContext()->Document()->
1040 0 : FlushPendingNotifications(FlushType::ContentAndNotify);
1041 0 : if (!weakThis.IsAlive())
1042 0 : return;
1043 :
1044 : // Redirect frame insertions during this method (see GetContentInsertionFrame())
1045 : // so that any reframing that the frame constructor forces upon us is inserted
1046 : // into the correct parent (mDisplayFrame). See bug 282607.
1047 0 : NS_PRECONDITION(!mInRedisplayText, "Nested RedisplayText");
1048 0 : mInRedisplayText = true;
1049 0 : mRedisplayTextEvent.Forget();
1050 :
1051 0 : ActuallyDisplayText(true);
1052 : // XXXbz This should perhaps be eResize. Check.
1053 0 : PresContext()->PresShell()->FrameNeedsReflow(mDisplayFrame,
1054 : nsIPresShell::eStyleChange,
1055 0 : NS_FRAME_IS_DIRTY);
1056 :
1057 0 : mInRedisplayText = false;
1058 : }
1059 :
1060 : void
1061 0 : nsComboboxControlFrame::ActuallyDisplayText(bool aNotify)
1062 : {
1063 0 : if (mDisplayedOptionTextOrPreview.IsEmpty()) {
1064 : // Have to use a non-breaking space for line-block-size calculations
1065 : // to be right
1066 : static const char16_t space = 0xA0;
1067 0 : mDisplayContent->SetText(&space, 1, aNotify);
1068 : } else {
1069 0 : mDisplayContent->SetText(mDisplayedOptionTextOrPreview, aNotify);
1070 : }
1071 0 : }
1072 :
1073 : int32_t
1074 0 : nsComboboxControlFrame::GetIndexOfDisplayArea()
1075 : {
1076 0 : return mDisplayedIndex;
1077 : }
1078 :
1079 : //----------------------------------------------------------------------
1080 : // nsISelectControlFrame
1081 : //----------------------------------------------------------------------
1082 : NS_IMETHODIMP
1083 0 : nsComboboxControlFrame::DoneAddingChildren(bool aIsDone)
1084 : {
1085 0 : nsISelectControlFrame* listFrame = do_QueryFrame(mDropdownFrame);
1086 0 : if (!listFrame)
1087 0 : return NS_ERROR_FAILURE;
1088 :
1089 0 : return listFrame->DoneAddingChildren(aIsDone);
1090 : }
1091 :
1092 : NS_IMETHODIMP
1093 0 : nsComboboxControlFrame::AddOption(int32_t aIndex)
1094 : {
1095 0 : if (aIndex <= mDisplayedIndex) {
1096 0 : ++mDisplayedIndex;
1097 : }
1098 :
1099 0 : nsListControlFrame* lcf = static_cast<nsListControlFrame*>(mDropdownFrame);
1100 0 : return lcf->AddOption(aIndex);
1101 : }
1102 :
1103 :
1104 : NS_IMETHODIMP
1105 0 : nsComboboxControlFrame::RemoveOption(int32_t aIndex)
1106 : {
1107 0 : AutoWeakFrame weakThis(this);
1108 0 : if (mListControlFrame->GetNumberOfOptions() > 0) {
1109 0 : if (aIndex < mDisplayedIndex) {
1110 0 : --mDisplayedIndex;
1111 0 : } else if (aIndex == mDisplayedIndex) {
1112 0 : mDisplayedIndex = 0; // IE6 compat
1113 0 : RedisplayText();
1114 : }
1115 : }
1116 : else {
1117 : // If we removed the last option, we need to blank things out
1118 0 : mDisplayedIndex = -1;
1119 0 : RedisplayText();
1120 : }
1121 :
1122 0 : if (!weakThis.IsAlive())
1123 0 : return NS_OK;
1124 :
1125 0 : nsListControlFrame* lcf = static_cast<nsListControlFrame*>(mDropdownFrame);
1126 0 : return lcf->RemoveOption(aIndex);
1127 : }
1128 :
1129 : NS_IMETHODIMP
1130 0 : nsComboboxControlFrame::OnSetSelectedIndex(int32_t aOldIndex, int32_t aNewIndex)
1131 : {
1132 0 : nsAutoScriptBlocker scriptBlocker;
1133 0 : mDisplayedIndex = aNewIndex;
1134 0 : RedisplayText();
1135 0 : NS_ASSERTION(mDropdownFrame, "No dropdown frame!");
1136 :
1137 0 : nsISelectControlFrame* listFrame = do_QueryFrame(mDropdownFrame);
1138 0 : NS_ASSERTION(listFrame, "No list frame!");
1139 :
1140 0 : return listFrame->OnSetSelectedIndex(aOldIndex, aNewIndex);
1141 : }
1142 :
1143 : // End nsISelectControlFrame
1144 : //----------------------------------------------------------------------
1145 :
1146 : nsresult
1147 0 : nsComboboxControlFrame::HandleEvent(nsPresContext* aPresContext,
1148 : WidgetGUIEvent* aEvent,
1149 : nsEventStatus* aEventStatus)
1150 : {
1151 0 : NS_ENSURE_ARG_POINTER(aEventStatus);
1152 :
1153 0 : if (nsEventStatus_eConsumeNoDefault == *aEventStatus) {
1154 0 : return NS_OK;
1155 : }
1156 :
1157 0 : EventStates eventStates = mContent->AsElement()->State();
1158 0 : if (eventStates.HasState(NS_EVENT_STATE_DISABLED)) {
1159 0 : return NS_OK;
1160 : }
1161 :
1162 : #if COMBOBOX_ROLLUP_CONSUME_EVENT == 0
1163 : if (aEvent->mMessage == eMouseDown) {
1164 : if (GetContent() == mozilla::widget::nsAutoRollup::GetLastRollup()) {
1165 : // This event did a Rollup on this control - prevent it from opening
1166 : // the dropdown again!
1167 : *aEventStatus = nsEventStatus_eConsumeNoDefault;
1168 : return NS_OK;
1169 : }
1170 : }
1171 : #endif
1172 :
1173 : // If we have style that affects how we are selected, feed event down to
1174 : // nsFrame::HandleEvent so that selection takes place when appropriate.
1175 0 : const nsStyleUserInterface* uiStyle = StyleUserInterface();
1176 0 : if (uiStyle->mUserInput == StyleUserInput::None ||
1177 0 : uiStyle->mUserInput == StyleUserInput::Disabled) {
1178 0 : return nsBlockFrame::HandleEvent(aPresContext, aEvent, aEventStatus);
1179 : }
1180 0 : return NS_OK;
1181 : }
1182 :
1183 :
1184 : nsresult
1185 0 : nsComboboxControlFrame::SetFormProperty(nsIAtom* aName, const nsAString& aValue)
1186 : {
1187 0 : nsIFormControlFrame* fcFrame = do_QueryFrame(mDropdownFrame);
1188 0 : if (!fcFrame) {
1189 0 : return NS_NOINTERFACE;
1190 : }
1191 :
1192 0 : return fcFrame->SetFormProperty(aName, aValue);
1193 : }
1194 :
1195 : nsContainerFrame*
1196 0 : nsComboboxControlFrame::GetContentInsertionFrame() {
1197 0 : return mInRedisplayText ? mDisplayFrame : mDropdownFrame->GetContentInsertionFrame();
1198 : }
1199 :
1200 : void
1201 0 : nsComboboxControlFrame::AppendDirectlyOwnedAnonBoxes(nsTArray<OwnedAnonBox>& aResult)
1202 : {
1203 0 : aResult.AppendElement(OwnedAnonBox(mDropdownFrame));
1204 0 : aResult.AppendElement(OwnedAnonBox(mDisplayFrame));
1205 0 : }
1206 :
1207 : nsresult
1208 0 : nsComboboxControlFrame::CreateAnonymousContent(nsTArray<ContentInfo>& aElements)
1209 : {
1210 : // The frames used to display the combo box and the button used to popup the dropdown list
1211 : // are created through anonymous content. The dropdown list is not created through anonymous
1212 : // content because its frame is initialized specifically for the drop-down case and it is placed
1213 : // a special list referenced through NS_COMBO_FRAME_POPUP_LIST_INDEX to keep separate from the
1214 : // layout of the display and button.
1215 : //
1216 : // Note: The value attribute of the display content is set when an item is selected in the dropdown list.
1217 : // If the content specified below does not honor the value attribute than nothing will be displayed.
1218 :
1219 : // For now the content that is created corresponds to two input buttons. It would be better to create the
1220 : // tag as something other than input, but then there isn't any way to create a button frame since it
1221 : // isn't possible to set the display type in CSS2 to create a button frame.
1222 :
1223 : // create content used for display
1224 : //nsIAtom* tag = NS_Atomize("mozcombodisplay");
1225 :
1226 : // Add a child text content node for the label
1227 :
1228 0 : nsNodeInfoManager *nimgr = mContent->NodeInfo()->NodeInfoManager();
1229 :
1230 0 : mDisplayContent = new nsTextNode(nimgr);
1231 :
1232 : // set the value of the text node
1233 0 : mDisplayedIndex = mListControlFrame->GetSelectedIndex();
1234 0 : if (mDisplayedIndex != -1) {
1235 0 : mListControlFrame->GetOptionText(mDisplayedIndex, mDisplayedOptionTextOrPreview);
1236 : }
1237 0 : ActuallyDisplayText(false);
1238 :
1239 0 : if (!aElements.AppendElement(mDisplayContent))
1240 0 : return NS_ERROR_OUT_OF_MEMORY;
1241 :
1242 0 : mButtonContent = mContent->OwnerDoc()->CreateHTMLElement(nsGkAtoms::button);
1243 0 : if (!mButtonContent)
1244 0 : return NS_ERROR_OUT_OF_MEMORY;
1245 :
1246 : // make someone to listen to the button. If its pressed by someone like Accessibility
1247 : // then open or close the combo box.
1248 0 : mButtonListener = new nsComboButtonListener(this);
1249 0 : mButtonContent->AddEventListener(NS_LITERAL_STRING("click"), mButtonListener,
1250 0 : false, false);
1251 :
1252 0 : mButtonContent->SetAttr(kNameSpaceID_None, nsGkAtoms::type,
1253 0 : NS_LITERAL_STRING("button"), false);
1254 : // Set tabindex="-1" so that the button is not tabbable
1255 0 : mButtonContent->SetAttr(kNameSpaceID_None, nsGkAtoms::tabindex,
1256 0 : NS_LITERAL_STRING("-1"), false);
1257 :
1258 0 : WritingMode wm = GetWritingMode();
1259 0 : if (wm.IsVertical()) {
1260 0 : mButtonContent->SetAttr(kNameSpaceID_None, nsGkAtoms::orientation,
1261 0 : wm.IsVerticalRL() ? NS_LITERAL_STRING("left")
1262 0 : : NS_LITERAL_STRING("right"),
1263 0 : false);
1264 : }
1265 :
1266 0 : if (!aElements.AppendElement(mButtonContent))
1267 0 : return NS_ERROR_OUT_OF_MEMORY;
1268 :
1269 0 : return NS_OK;
1270 : }
1271 :
1272 : void
1273 0 : nsComboboxControlFrame::AppendAnonymousContentTo(nsTArray<nsIContent*>& aElements,
1274 : uint32_t aFilter)
1275 : {
1276 0 : if (mDisplayContent) {
1277 0 : aElements.AppendElement(mDisplayContent);
1278 : }
1279 :
1280 0 : if (mButtonContent) {
1281 0 : aElements.AppendElement(mButtonContent);
1282 : }
1283 0 : }
1284 :
1285 : // XXXbz this is a for-now hack. Now that display:inline-block works,
1286 : // need to revisit this.
1287 0 : class nsComboboxDisplayFrame : public nsBlockFrame {
1288 : public:
1289 0 : NS_DECL_FRAMEARENA_HELPERS(nsComboboxDisplayFrame)
1290 :
1291 0 : nsComboboxDisplayFrame(nsStyleContext* aContext,
1292 : nsComboboxControlFrame* aComboBox)
1293 0 : : nsBlockFrame(aContext, kClassID)
1294 0 : , mComboBox(aComboBox)
1295 0 : {}
1296 :
1297 : #ifdef DEBUG_FRAME_DUMP
1298 0 : nsresult GetFrameName(nsAString& aResult) const override
1299 : {
1300 0 : return MakeFrameName(NS_LITERAL_STRING("ComboboxDisplay"), aResult);
1301 : }
1302 : #endif
1303 :
1304 0 : virtual bool IsFrameOfType(uint32_t aFlags) const override
1305 : {
1306 0 : return nsBlockFrame::IsFrameOfType(aFlags &
1307 0 : ~(nsIFrame::eReplacedContainsBlock));
1308 : }
1309 :
1310 : virtual void Reflow(nsPresContext* aPresContext,
1311 : ReflowOutput& aDesiredSize,
1312 : const ReflowInput& aReflowInput,
1313 : nsReflowStatus& aStatus) override;
1314 :
1315 : virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder,
1316 : const nsRect& aDirtyRect,
1317 : const nsDisplayListSet& aLists) override;
1318 :
1319 : protected:
1320 : nsComboboxControlFrame* mComboBox;
1321 : };
1322 :
1323 0 : NS_IMPL_FRAMEARENA_HELPERS(nsComboboxDisplayFrame)
1324 :
1325 : void
1326 0 : nsComboboxDisplayFrame::Reflow(nsPresContext* aPresContext,
1327 : ReflowOutput& aDesiredSize,
1328 : const ReflowInput& aReflowInput,
1329 : nsReflowStatus& aStatus)
1330 : {
1331 0 : ReflowInput state(aReflowInput);
1332 0 : if (state.ComputedBSize() == NS_INTRINSICSIZE) {
1333 : // Note that the only way we can have a computed block size here is
1334 : // if the combobox had a specified block size. If it didn't, size
1335 : // based on what our rows look like, for lack of anything better.
1336 0 : state.SetComputedBSize(mComboBox->mListControlFrame->GetBSizeOfARow());
1337 : }
1338 0 : WritingMode wm = aReflowInput.GetWritingMode();
1339 0 : nscoord computedISize = mComboBox->mDisplayISize -
1340 0 : state.ComputedLogicalBorderPadding().IStartEnd(wm);
1341 0 : if (computedISize < 0) {
1342 0 : computedISize = 0;
1343 : }
1344 0 : state.SetComputedISize(computedISize);
1345 0 : nsBlockFrame::Reflow(aPresContext, aDesiredSize, state, aStatus);
1346 0 : aStatus.Reset(); // this type of frame can't be split
1347 0 : }
1348 :
1349 : void
1350 0 : nsComboboxDisplayFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
1351 : const nsRect& aDirtyRect,
1352 : const nsDisplayListSet& aLists)
1353 : {
1354 0 : nsDisplayListCollection set;
1355 0 : nsBlockFrame::BuildDisplayList(aBuilder, aDirtyRect, set);
1356 :
1357 : // remove background items if parent frame is themed
1358 0 : if (mComboBox->IsThemed()) {
1359 0 : set.BorderBackground()->DeleteAll();
1360 : }
1361 :
1362 0 : set.MoveTo(aLists);
1363 0 : }
1364 :
1365 : nsIFrame*
1366 0 : nsComboboxControlFrame::CreateFrameForDisplayNode()
1367 : {
1368 0 : MOZ_ASSERT(mDisplayContent);
1369 :
1370 : // Get PresShell
1371 0 : nsIPresShell *shell = PresContext()->PresShell();
1372 0 : StyleSetHandle styleSet = shell->StyleSet();
1373 :
1374 : // create the style contexts for the anonymous block frame and text frame
1375 0 : RefPtr<nsStyleContext> styleContext;
1376 : styleContext = styleSet->
1377 0 : ResolveInheritingAnonymousBoxStyle(nsCSSAnonBoxes::mozDisplayComboboxControlFrame,
1378 0 : mStyleContext);
1379 :
1380 0 : RefPtr<nsStyleContext> textStyleContext;
1381 : textStyleContext =
1382 0 : styleSet->ResolveStyleForText(mDisplayContent, mStyleContext);
1383 :
1384 : // Start by creating our anonymous block frame
1385 0 : mDisplayFrame = new (shell) nsComboboxDisplayFrame(styleContext, this);
1386 0 : mDisplayFrame->Init(mContent, this, nullptr);
1387 :
1388 : // Create a text frame and put it inside the block frame
1389 0 : nsIFrame* textFrame = NS_NewTextFrame(shell, textStyleContext);
1390 :
1391 : // initialize the text frame
1392 0 : textFrame->Init(mDisplayContent, mDisplayFrame, nullptr);
1393 0 : mDisplayContent->SetPrimaryFrame(textFrame);
1394 :
1395 0 : nsFrameList textList(textFrame, textFrame);
1396 0 : mDisplayFrame->SetInitialChildList(kPrincipalList, textList);
1397 0 : return mDisplayFrame;
1398 : }
1399 :
1400 : void
1401 0 : nsComboboxControlFrame::DestroyFrom(nsIFrame* aDestructRoot)
1402 : {
1403 0 : if (sFocused == this) {
1404 0 : sFocused = nullptr;
1405 : }
1406 :
1407 : // Revoke any pending RedisplayTextEvent
1408 0 : mRedisplayTextEvent.Revoke();
1409 :
1410 0 : nsFormControlFrame::RegUnRegAccessKey(static_cast<nsIFrame*>(this), false);
1411 :
1412 0 : if (mDroppedDown) {
1413 0 : MOZ_ASSERT(mDropdownFrame, "mDroppedDown without frame");
1414 0 : nsView* view = mDropdownFrame->GetView();
1415 0 : MOZ_ASSERT(view);
1416 0 : nsIWidget* widget = view->GetWidget();
1417 0 : if (widget) {
1418 0 : widget->CaptureRollupEvents(this, false);
1419 : }
1420 : }
1421 :
1422 : // Cleanup frames in popup child list
1423 0 : mPopupFrames.DestroyFramesFrom(aDestructRoot);
1424 0 : nsContentUtils::DestroyAnonymousContent(&mDisplayContent);
1425 0 : nsContentUtils::DestroyAnonymousContent(&mButtonContent);
1426 0 : nsBlockFrame::DestroyFrom(aDestructRoot);
1427 0 : }
1428 :
1429 : const nsFrameList&
1430 0 : nsComboboxControlFrame::GetChildList(ChildListID aListID) const
1431 : {
1432 0 : if (kSelectPopupList == aListID) {
1433 0 : return mPopupFrames;
1434 : }
1435 0 : return nsBlockFrame::GetChildList(aListID);
1436 : }
1437 :
1438 : void
1439 0 : nsComboboxControlFrame::GetChildLists(nsTArray<ChildList>* aLists) const
1440 : {
1441 0 : nsBlockFrame::GetChildLists(aLists);
1442 0 : mPopupFrames.AppendIfNonempty(aLists, kSelectPopupList);
1443 0 : }
1444 :
1445 : void
1446 0 : nsComboboxControlFrame::SetInitialChildList(ChildListID aListID,
1447 : nsFrameList& aChildList)
1448 : {
1449 0 : if (kSelectPopupList == aListID) {
1450 0 : mPopupFrames.SetFrames(aChildList);
1451 : } else {
1452 0 : for (nsFrameList::Enumerator e(aChildList); !e.AtEnd(); e.Next()) {
1453 : nsCOMPtr<nsIFormControl> formControl =
1454 0 : do_QueryInterface(e.get()->GetContent());
1455 0 : if (formControl && formControl->ControlType() == NS_FORM_BUTTON_BUTTON) {
1456 0 : mButtonFrame = e.get();
1457 0 : break;
1458 : }
1459 : }
1460 0 : NS_ASSERTION(mButtonFrame, "missing button frame in initial child list");
1461 0 : nsBlockFrame::SetInitialChildList(aListID, aChildList);
1462 : }
1463 0 : }
1464 :
1465 : //----------------------------------------------------------------------
1466 : //nsIRollupListener
1467 : //----------------------------------------------------------------------
1468 : bool
1469 0 : nsComboboxControlFrame::Rollup(uint32_t aCount, bool aFlush,
1470 : const nsIntPoint* pos, nsIContent** aLastRolledUp)
1471 : {
1472 0 : if (aLastRolledUp) {
1473 0 : *aLastRolledUp = nullptr;
1474 : }
1475 :
1476 0 : if (!mDroppedDown) {
1477 0 : return false;
1478 : }
1479 :
1480 0 : bool consume = !!COMBOBOX_ROLLUP_CONSUME_EVENT;
1481 0 : AutoWeakFrame weakFrame(this);
1482 0 : mListControlFrame->AboutToRollup(); // might destroy us
1483 0 : if (!weakFrame.IsAlive()) {
1484 0 : return consume;
1485 : }
1486 0 : ShowDropDown(false); // might destroy us
1487 0 : if (weakFrame.IsAlive()) {
1488 0 : mListControlFrame->CaptureMouseEvents(false);
1489 : }
1490 :
1491 0 : if (aFlush && weakFrame.IsAlive()) {
1492 : // The popup's visibility doesn't update until the minimize animation has
1493 : // finished, so call UpdateWidgetGeometry to update it right away.
1494 0 : nsViewManager* viewManager = mDropdownFrame->GetView()->GetViewManager();
1495 0 : viewManager->UpdateWidgetGeometry(); // might destroy us
1496 : }
1497 :
1498 0 : if (!weakFrame.IsAlive()) {
1499 0 : return consume;
1500 : }
1501 :
1502 0 : if (aLastRolledUp) {
1503 0 : *aLastRolledUp = GetContent();
1504 : }
1505 0 : return consume;
1506 : }
1507 :
1508 : nsIWidget*
1509 0 : nsComboboxControlFrame::GetRollupWidget()
1510 : {
1511 0 : nsView* view = mDropdownFrame->GetView();
1512 0 : MOZ_ASSERT(view);
1513 0 : return view->GetWidget();
1514 : }
1515 :
1516 : void
1517 0 : nsComboboxControlFrame::RollupFromList()
1518 : {
1519 0 : if (ShowList(false))
1520 0 : mListControlFrame->CaptureMouseEvents(false);
1521 0 : }
1522 :
1523 : int32_t
1524 0 : nsComboboxControlFrame::UpdateRecentIndex(int32_t aIndex)
1525 : {
1526 0 : int32_t index = mRecentSelectedIndex;
1527 0 : if (mRecentSelectedIndex == NS_SKIP_NOTIFY_INDEX || aIndex == NS_SKIP_NOTIFY_INDEX)
1528 0 : mRecentSelectedIndex = aIndex;
1529 0 : return index;
1530 : }
1531 :
1532 : class nsDisplayComboboxFocus : public nsDisplayItem {
1533 : public:
1534 0 : nsDisplayComboboxFocus(nsDisplayListBuilder* aBuilder,
1535 : nsComboboxControlFrame* aFrame)
1536 0 : : nsDisplayItem(aBuilder, aFrame) {
1537 0 : MOZ_COUNT_CTOR(nsDisplayComboboxFocus);
1538 0 : }
1539 : #ifdef NS_BUILD_REFCNT_LOGGING
1540 0 : virtual ~nsDisplayComboboxFocus() {
1541 0 : MOZ_COUNT_DTOR(nsDisplayComboboxFocus);
1542 0 : }
1543 : #endif
1544 :
1545 : virtual void Paint(nsDisplayListBuilder* aBuilder,
1546 : gfxContext* aCtx) override;
1547 0 : NS_DISPLAY_DECL_NAME("ComboboxFocus", TYPE_COMBOBOX_FOCUS)
1548 : };
1549 :
1550 0 : void nsDisplayComboboxFocus::Paint(nsDisplayListBuilder* aBuilder,
1551 : gfxContext* aCtx)
1552 : {
1553 0 : static_cast<nsComboboxControlFrame*>(mFrame)
1554 0 : ->PaintFocus(*aCtx->GetDrawTarget(), ToReferenceFrame());
1555 0 : }
1556 :
1557 : void
1558 0 : nsComboboxControlFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
1559 : const nsRect& aDirtyRect,
1560 : const nsDisplayListSet& aLists)
1561 : {
1562 : #ifdef NOISY
1563 : printf("%p paint at (%d, %d, %d, %d)\n", this,
1564 : aDirtyRect.x, aDirtyRect.y, aDirtyRect.width, aDirtyRect.height);
1565 : #endif
1566 :
1567 0 : if (aBuilder->IsForEventDelivery()) {
1568 : // Don't allow children to receive events.
1569 : // REVIEW: following old GetFrameForPoint
1570 0 : DisplayBorderBackgroundOutline(aBuilder, aLists);
1571 : } else {
1572 : // REVIEW: Our in-flow child frames are inline-level so they will paint in our
1573 : // content list, so we don't need to mess with layers.
1574 0 : nsBlockFrame::BuildDisplayList(aBuilder, aDirtyRect, aLists);
1575 : }
1576 :
1577 : // draw a focus indicator only when focus rings should be drawn
1578 0 : nsIDocument* doc = mContent->GetComposedDoc();
1579 0 : if (doc) {
1580 0 : nsPIDOMWindowOuter* window = doc->GetWindow();
1581 0 : if (window && window->ShouldShowFocusRing()) {
1582 0 : nsPresContext *presContext = PresContext();
1583 0 : const nsStyleDisplay *disp = StyleDisplay();
1584 0 : if ((!IsThemed(disp) ||
1585 0 : !presContext->GetTheme()->ThemeDrawsFocusForWidget(disp->mAppearance)) &&
1586 0 : mDisplayFrame && IsVisibleForPainting(aBuilder)) {
1587 0 : aLists.Content()->AppendNewToTop(
1588 0 : new (aBuilder) nsDisplayComboboxFocus(aBuilder, this));
1589 : }
1590 : }
1591 : }
1592 :
1593 0 : DisplaySelectionOverlay(aBuilder, aLists.Content());
1594 0 : }
1595 :
1596 0 : void nsComboboxControlFrame::PaintFocus(DrawTarget& aDrawTarget, nsPoint aPt)
1597 : {
1598 : /* Do we need to do anything? */
1599 0 : EventStates eventStates = mContent->AsElement()->State();
1600 0 : if (eventStates.HasState(NS_EVENT_STATE_DISABLED) || sFocused != this)
1601 0 : return;
1602 :
1603 0 : int32_t appUnitsPerDevPixel = PresContext()->AppUnitsPerDevPixel();
1604 :
1605 0 : nsRect clipRect = mDisplayFrame->GetRect() + aPt;
1606 0 : aDrawTarget.PushClipRect(NSRectToSnappedRect(clipRect,
1607 : appUnitsPerDevPixel,
1608 0 : aDrawTarget));
1609 :
1610 : // REVIEW: Why does the old code paint mDisplayFrame again? We've
1611 : // already painted it in the children above. So clipping it here won't do
1612 : // us much good.
1613 :
1614 : /////////////////////
1615 : // draw focus
1616 :
1617 0 : StrokeOptions strokeOptions;
1618 0 : nsLayoutUtils::InitDashPattern(strokeOptions, NS_STYLE_BORDER_STYLE_DOTTED);
1619 0 : ColorPattern color(ToDeviceColor(StyleColor()->mColor));
1620 0 : nscoord onePixel = nsPresContext::CSSPixelsToAppUnits(1);
1621 0 : clipRect.width -= onePixel;
1622 0 : clipRect.height -= onePixel;
1623 0 : Rect r = ToRect(nsLayoutUtils::RectToGfxRect(clipRect, appUnitsPerDevPixel));
1624 0 : StrokeSnappedEdgesOfRect(r, aDrawTarget, color, strokeOptions);
1625 :
1626 0 : aDrawTarget.PopClip();
1627 : }
1628 :
1629 : //---------------------------------------------------------
1630 : // gets the content (an option) by index and then set it as
1631 : // being selected or not selected
1632 : //---------------------------------------------------------
1633 : NS_IMETHODIMP
1634 0 : nsComboboxControlFrame::OnOptionSelected(int32_t aIndex, bool aSelected)
1635 : {
1636 0 : if (mDroppedDown) {
1637 0 : nsISelectControlFrame *selectFrame = do_QueryFrame(mListControlFrame);
1638 0 : if (selectFrame) {
1639 0 : selectFrame->OnOptionSelected(aIndex, aSelected);
1640 : }
1641 : } else {
1642 0 : if (aSelected) {
1643 0 : nsAutoScriptBlocker blocker;
1644 0 : mDisplayedIndex = aIndex;
1645 0 : RedisplayText();
1646 : } else {
1647 0 : AutoWeakFrame weakFrame(this);
1648 0 : RedisplaySelectedText();
1649 0 : if (weakFrame.IsAlive()) {
1650 0 : FireValueChangeEvent(); // Fire after old option is unselected
1651 : }
1652 : }
1653 : }
1654 :
1655 0 : return NS_OK;
1656 : }
1657 :
1658 0 : void nsComboboxControlFrame::FireValueChangeEvent()
1659 : {
1660 : // Fire ValueChange event to indicate data value of combo box has changed
1661 0 : nsContentUtils::AddScriptRunner(
1662 0 : new AsyncEventDispatcher(mContent, NS_LITERAL_STRING("ValueChange"), true,
1663 0 : false));
1664 0 : }
1665 :
1666 : void
1667 0 : nsComboboxControlFrame::OnContentReset()
1668 : {
1669 0 : if (mListControlFrame) {
1670 0 : mListControlFrame->OnContentReset();
1671 : }
1672 0 : }
1673 :
1674 :
1675 : //--------------------------------------------------------
1676 : // nsIStatefulFrame
1677 : //--------------------------------------------------------
1678 : NS_IMETHODIMP
1679 0 : nsComboboxControlFrame::SaveState(nsPresState** aState)
1680 : {
1681 0 : MOZ_ASSERT(!(*aState));
1682 0 : (*aState) = new nsPresState();
1683 0 : (*aState)->SetDroppedDown(mDroppedDown);
1684 0 : return NS_OK;
1685 : }
1686 :
1687 : NS_IMETHODIMP
1688 0 : nsComboboxControlFrame::RestoreState(nsPresState* aState)
1689 : {
1690 0 : if (!aState) {
1691 0 : return NS_ERROR_FAILURE;
1692 : }
1693 0 : ShowList(aState->GetDroppedDown()); // might destroy us
1694 0 : return NS_OK;
1695 : }
1696 :
1697 : // Append a suffix so that the state key for the combobox is different
1698 : // from the state key the list control uses to sometimes save the scroll
1699 : // position for the same Element
1700 : NS_IMETHODIMP
1701 0 : nsComboboxControlFrame::GenerateStateKey(nsIContent* aContent,
1702 : nsIDocument* aDocument,
1703 : nsACString& aKey)
1704 : {
1705 0 : nsresult rv = nsContentUtils::GenerateStateKey(aContent, aDocument, aKey);
1706 0 : if (NS_FAILED(rv) || aKey.IsEmpty()) {
1707 0 : return rv;
1708 : }
1709 0 : aKey.Append("CCF");
1710 0 : return NS_OK;
1711 : }
1712 :
1713 : // Fennec uses a custom combobox built-in widget.
1714 : //
1715 :
1716 : /* static */
1717 : bool
1718 0 : nsComboboxControlFrame::ToolkitHasNativePopup()
1719 : {
1720 : #ifdef MOZ_USE_NATIVE_POPUP_WINDOWS
1721 : return true;
1722 : #else
1723 0 : return false;
1724 : #endif /* MOZ_USE_NATIVE_POPUP_WINDOWS */
1725 : }
1726 :
|